summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarcus Lundblad <malu@pidgin.im>2009-11-09 19:27:45 +0000
committerMarcus Lundblad <malu@pidgin.im>2009-11-09 19:27:45 +0000
commitae94017ae540920b107d66d0e081b736df283602 (patch)
tree552b7c8c5c29f67e6f85909126486da581e872d1
parent5872d785f9735d7e62b5084e93fe03f8e7ed1ebf (diff)
parentc9d7a82a21f207276c44a24b31e92b1885a0b693 (diff)
downloadpidgin-ae94017ae540920b107d66d0e081b736df283602.tar.gz
merge of '11123464441f3f8314ef91a143f9c1a853c231e1'
and '54f3151459432309ef06705f4046e90475440c3f'
-rw-r--r--AUTHORS1
-rw-r--r--COPYRIGHT9
-rw-r--r--ChangeLog62
-rw-r--r--ChangeLog.API3
-rw-r--r--ChangeLog.win323
-rw-r--r--NEWS5
-rwxr-xr-xautogen.sh12
-rw-r--r--configure.ac29
-rw-r--r--doc/pidgin.1.in2
-rw-r--r--finch/gntblist.c2
-rw-r--r--finch/gntconn.c16
-rw-r--r--finch/gntconv.c45
-rw-r--r--finch/libgnt/gntbindable.h2
-rw-r--r--finch/libgnt/gntbox.c2
-rw-r--r--finch/libgnt/gntcolors.c9
-rw-r--r--finch/libgnt/gntentry.c9
-rw-r--r--finch/libgnt/gntfilesel.c5
-rw-r--r--finch/libgnt/gnttextview.c19
-rw-r--r--finch/libgnt/gnttree.c2
-rw-r--r--finch/libgnt/gntutils.c2
-rw-r--r--finch/plugins/gnttinyurl.c147
-rw-r--r--libpurple/Makefile.am2
-rw-r--r--libpurple/account.c15
-rw-r--r--libpurple/blist.c16
-rw-r--r--libpurple/certificate.c18
-rw-r--r--libpurple/cipher.c4
-rw-r--r--libpurple/dnsquery.c8
-rw-r--r--libpurple/dnsquery.h5
-rw-r--r--libpurple/dnssrv.c106
-rw-r--r--libpurple/ft.c2
-rw-r--r--libpurple/ft.h2
-rw-r--r--libpurple/media.c43
-rwxr-xr-xlibpurple/plugins/perl/Makefile.mingw4
-rw-r--r--libpurple/plugins/perl/common/Makefile.mingw5
-rw-r--r--libpurple/plugins/perl/common/Prpl.xs12
-rw-r--r--libpurple/plugins/perl/perl-handlers.c2
-rw-r--r--libpurple/plugins/perl/perl.c3
-rw-r--r--libpurple/plugins/ssl/ssl-gnutls.c7
-rw-r--r--libpurple/protocols/Makefile.am2
-rw-r--r--libpurple/protocols/bonjour/mdns_avahi.c8
-rw-r--r--libpurple/protocols/jabber/JEPS27
-rw-r--r--libpurple/protocols/jabber/XEPS4
-rw-r--r--libpurple/protocols/jabber/auth.c32
-rw-r--r--libpurple/protocols/jabber/bosh.c3
-rw-r--r--libpurple/protocols/jabber/buddy.c16
-rw-r--r--libpurple/protocols/jabber/caps.c2
-rw-r--r--libpurple/protocols/jabber/chat.c168
-rw-r--r--libpurple/protocols/jabber/chat.h17
-rw-r--r--libpurple/protocols/jabber/disco.c75
-rw-r--r--libpurple/protocols/jabber/google.c56
-rw-r--r--libpurple/protocols/jabber/google.h4
-rw-r--r--libpurple/protocols/jabber/iq.c4
-rw-r--r--libpurple/protocols/jabber/jabber.c126
-rw-r--r--libpurple/protocols/jabber/jabber.h17
-rw-r--r--libpurple/protocols/jabber/jingle/content.c4
-rw-r--r--libpurple/protocols/jabber/libxmpp.c4
-rw-r--r--libpurple/protocols/jabber/message.c23
-rw-r--r--libpurple/protocols/jabber/oob.c4
-rw-r--r--libpurple/protocols/jabber/parser.c17
-rw-r--r--libpurple/protocols/jabber/presence.c21
-rw-r--r--libpurple/protocols/jabber/roster.c45
-rw-r--r--libpurple/protocols/jabber/si.c6
-rw-r--r--libpurple/protocols/jabber/useravatar.c2
-rw-r--r--libpurple/protocols/msn/contact.c47
-rw-r--r--libpurple/protocols/msn/msn.c34
-rw-r--r--libpurple/protocols/msn/msn.h1
-rw-r--r--libpurple/protocols/msn/nexus.c12
-rw-r--r--libpurple/protocols/msn/notification.c129
-rw-r--r--libpurple/protocols/msn/oim.c17
-rw-r--r--libpurple/protocols/msn/servconn.c20
-rw-r--r--libpurple/protocols/msn/servconn.h4
-rw-r--r--libpurple/protocols/msn/transaction.c9
-rw-r--r--libpurple/protocols/msn/transaction.h3
-rw-r--r--libpurple/protocols/msn/user.c5
-rw-r--r--libpurple/protocols/msn/userlist.c2
-rw-r--r--libpurple/protocols/mxit/Makefile.am63
-rw-r--r--libpurple/protocols/mxit/Makefile.mingw91
-rw-r--r--libpurple/protocols/mxit/actions.c437
-rw-r--r--libpurple/protocols/mxit/actions.h34
-rw-r--r--libpurple/protocols/mxit/aes.c405
-rw-r--r--libpurple/protocols/mxit/aes.h39
-rw-r--r--libpurple/protocols/mxit/chunk.c659
-rw-r--r--libpurple/protocols/mxit/chunk.h140
-rw-r--r--libpurple/protocols/mxit/cipher.c111
-rw-r--r--libpurple/protocols/mxit/cipher.h36
-rw-r--r--libpurple/protocols/mxit/filexfer.c454
-rw-r--r--libpurple/protocols/mxit/filexfer.h50
-rw-r--r--libpurple/protocols/mxit/formcmds.c397
-rw-r--r--libpurple/protocols/mxit/formcmds.h35
-rw-r--r--libpurple/protocols/mxit/http.c331
-rw-r--r--libpurple/protocols/mxit/http.h47
-rw-r--r--libpurple/protocols/mxit/login.c789
-rw-r--r--libpurple/protocols/mxit/login.h45
-rw-r--r--libpurple/protocols/mxit/markup.c1192
-rw-r--r--libpurple/protocols/mxit/markup.h40
-rw-r--r--libpurple/protocols/mxit/multimx.c597
-rw-r--r--libpurple/protocols/mxit/multimx.h104
-rw-r--r--libpurple/protocols/mxit/mxit.c694
-rw-r--r--libpurple/protocols/mxit/mxit.h197
-rw-r--r--libpurple/protocols/mxit/profile.c160
-rw-r--r--libpurple/protocols/mxit/profile.h56
-rw-r--r--libpurple/protocols/mxit/protocol.c2442
-rw-r--r--libpurple/protocols/mxit/protocol.h304
-rw-r--r--libpurple/protocols/mxit/roster.c722
-rw-r--r--libpurple/protocols/mxit/roster.h139
-rw-r--r--libpurple/protocols/mxit/splashscreen.c223
-rw-r--r--libpurple/protocols/mxit/splashscreen.h59
-rw-r--r--libpurple/protocols/oscar/clientlogin.c36
-rw-r--r--libpurple/protocols/oscar/family_feedbag.c11
-rw-r--r--libpurple/protocols/oscar/family_icbm.c53
-rw-r--r--libpurple/protocols/oscar/family_oservice.c53
-rw-r--r--libpurple/protocols/oscar/flap_connection.c18
-rw-r--r--libpurple/protocols/oscar/libaim.c2
-rw-r--r--libpurple/protocols/oscar/libicq.c2
-rw-r--r--libpurple/protocols/oscar/oscar.c316
-rw-r--r--libpurple/protocols/oscar/oscar.h13
-rw-r--r--libpurple/protocols/oscar/oscarcommon.h2
-rw-r--r--libpurple/protocols/silc/Makefile.mingw4
-rw-r--r--libpurple/protocols/silc10/Makefile.mingw2
-rw-r--r--libpurple/protocols/yahoo/libyahoo.c2
-rw-r--r--libpurple/protocols/yahoo/libymsg.c606
-rw-r--r--libpurple/protocols/yahoo/libymsg.h15
-rw-r--r--libpurple/protocols/yahoo/util.c18
-rw-r--r--libpurple/protocols/yahoo/yahoo_filexfer.c7
-rw-r--r--libpurple/protocols/yahoo/yahoo_filexfer.h12
-rw-r--r--libpurple/protocols/yahoo/yahoo_friend.c43
-rw-r--r--libpurple/protocols/yahoo/yahoo_friend.h3
-rw-r--r--libpurple/server.c14
-rw-r--r--libpurple/tests/test_jabber_jutil.c8
-rw-r--r--libpurple/tests/test_yahoo_util.c6
-rw-r--r--libpurple/theme-loader.c1
-rw-r--r--libpurple/win32/global.mak2
-rw-r--r--pidgin.desktop.in2
-rw-r--r--pidgin/Makefile.mingw10
-rw-r--r--pidgin/gtkblist.c6
-rw-r--r--pidgin/gtkconn.c12
-rw-r--r--pidgin/gtkconv.c29
-rw-r--r--pidgin/gtkdialogs.c99
-rw-r--r--pidgin/gtkimhtml.c5
-rw-r--r--pidgin/gtkmain.c102
-rw-r--r--pidgin/gtkmedia.c20
-rw-r--r--pidgin/gtkpounce.c2
-rw-r--r--pidgin/gtkprefs.c50
-rw-r--r--pidgin/gtkrequest.c35
-rw-r--r--pidgin/gtkroomlist.c11
-rw-r--r--pidgin/gtkstatusbox.c208
-rw-r--r--pidgin/gtkutils.c57
-rw-r--r--pidgin/pixmaps/Makefile.am3
-rw-r--r--pidgin/pixmaps/emotes/default/24/default.theme.in2
-rw-r--r--pidgin/pixmaps/emotes/small/16/small.theme.in2
-rw-r--r--pidgin/pixmaps/protocols/16/mxit.pngbin0 -> 490 bytes
-rw-r--r--pidgin/pixmaps/protocols/22/mxit.pngbin0 -> 725 bytes
-rw-r--r--pidgin/pixmaps/protocols/48/mxit.pngbin0 -> 1689 bytes
-rw-r--r--pidgin/pixmaps/protocols/scalable/mxit.svg24
-rw-r--r--pidgin/plugins/disco/gtkdisco.c18
-rw-r--r--pidgin/plugins/disco/gtkdisco.h2
-rw-r--r--pidgin/plugins/perl/common/Makefile.mingw5
-rw-r--r--pidgin/plugins/win32/winprefs/Makefile.mingw1
-rw-r--r--pidgin/plugins/xmppconsole.c1
-rw-r--r--pidgin/win32/nsis/pidgin-installer.nsi1
-rw-r--r--po/ChangeLog6
-rw-r--r--po/POTFILES.in9
-rw-r--r--po/ca.po322
-rw-r--r--po/de.po70
-rw-r--r--po/vi.po2985
-rw-r--r--share/ca-certs/Entrust.net_Secure_Server_CA.pem28
-rw-r--r--share/ca-certs/Makefile.am1
167 files changed, 14672 insertions, 3407 deletions
diff --git a/AUTHORS b/AUTHORS
index 92de8cb238..dccdb13a2d 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -20,6 +20,7 @@ Sean Egan - Developer
Casey Harkins - Developer
Gary 'grim' Kramlich - Developer
Richard 'rlaager' Laager - Developer
+Sulabh 'sulabh_m' Mahajan - Developer
Richard 'wabz' Nelson - Developer
Christopher 'siege' O'Brien - Developer
Bartosz Oler - Developer
diff --git a/COPYRIGHT b/COPYRIGHT
index 27df5699d9..a64ede71aa 100644
--- a/COPYRIGHT
+++ b/COPYRIGHT
@@ -268,6 +268,7 @@ Moses Lei
Ambrose C. Li
Nicolas Lichtmaier
Wesley Lin
+Shaun Lindsay
Artem Litvinovich
Josh Littlefield
Daniel Ljungborg
@@ -275,6 +276,7 @@ Syd Logan
Lokheed
Norberto Lopes
Shlomi Loubaton
+Pieter Loubser
Brian Lu
Uli Luckas
Matthew Luckie
@@ -318,6 +320,7 @@ Tim Mooney
Sergio Moretto
Andrei Mozzhuhin
Christian Muise
+MXit Lifestyle (Pty) Ltd.
Richard Nelson
Dennis Nezic
Matthew A. Nicholson
@@ -406,11 +409,10 @@ Alceste Scalas
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 @@ Torrey Searle
Peter Seebach
Don Seiler
Leonardo Serra
+Matteo Settenvini
+Colin Seymour
Jim Seymour
Javeed Shaikh
Joe Shaw
@@ -495,6 +499,7 @@ Kristof Vansant
James Vega
David Vermeille
Sid Vicious
+Andrew Victor
Jorge Villaseñor (Masca)
Bjoern Voigt
Wan Hing Wah
diff --git a/ChangeLog b/ChangeLog
index 88b9c5847b..e0c64e0659 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,18 +1,74 @@
+
Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
-version 2.6.3 (??/??/20??):
+version 2.6.4 (??/??/20??):
+ libpurple:
+ * Actually emit the hold signal for media calls.
+
+ General:
+ * New 'plugins' sub-command to 'debug' command (i.e. '/debug plugins')
+ to announce the list of loaded plugins (in both Finch and Pidgin).
+ * Fix 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
+ option for their buddies. (Eion Robb)
* 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:
+ * Fix sending /buzz.
+ * Fix blocking behavior for federated (MSN/OCS/Sametime) service users.
+ (Jason Cohen)
+ * 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
+ Captcha when joining chat rooms.
+
+ Pidgin:
+ * The userlist in a multiuser chat can be styled via gtkrc by using the
+ widget name "pidgin_conv_userlist". (Heiko Schmitt)
+ * Add a hold button to the media window.
+
+version 2.6.3 (10/16/2009):
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)
+ 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:
* Fix --disable-avahi to actually disable it in configure, as opposed
diff --git a/ChangeLog.API b/ChangeLog.API
index 1352e52542..aae1a36f93 100644
--- a/ChangeLog.API
+++ b/ChangeLog.API
@@ -1,5 +1,8 @@
Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
+version 2.6.3 (10/16/2009):
+ No changes
+
version 2.6.2 (09/05/2009):
Perl:
Added:
diff --git a/ChangeLog.win32 b/ChangeLog.win32
index ab30e7a6aa..654487585a 100644
--- a/ChangeLog.win32
+++ b/ChangeLog.win32
@@ -1,3 +1,6 @@
+version 2.6.3 (10/16/2009):
+ * No changes
+
version 2.6.2 (09/05/2009):
* No changes
diff --git a/NEWS b/NEWS
index f8d51fd87b..3e916ba28f 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,11 @@ Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
Our development blog is available at: http://planet.pidgin.im
+2.6.3 (10/16/2009):
+ Mark: Someone reported a fairly serious bug in our AIM/ICQ code
+ so we're releasing a special "severe bug fix only" build. See the
+ ChangeLog for details. Enjoy!
+
2.6.2 (09/05/2009):
Mark: Woo boy it's been a busy two weeks. There was a lot of new code
in 2.6.0, and with new code comes new bugs. The cadre of relentless
diff --git a/autogen.sh b/autogen.sh
index 6e4f436865..60b3cd8452 100755
--- a/autogen.sh
+++ b/autogen.sh
@@ -83,7 +83,7 @@ run_or_die () { # beotch
OUTPUT=`mktemp autogen-XXXXXX`
- printf "%s" "running ${CMD} ${@}... "
+ printf "running %s %s... " ${CMD} "$*"
${CMD} ${@} >${OUTPUT} 2>&1
if [ $? != 0 ] ; then
@@ -99,9 +99,17 @@ run_or_die () { # beotch
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 ${GLIB_GETTEXTIZE} ${GLIB_GETTEXTIZE_FLAGS:-"--force --copy"}
run_or_die ${INTLTOOLIZE} ${INTLTOOLIZE_FLAGS:-"-c -f --automake"}
# This call to sed is needed to work around an annoying bug in intltool 0.40.6
# See http://developer.pidgin.im/ticket/9520 for details
-run_or_die ${SED} "s:'\^\$\$lang\$\$':\^\$\$lang\$\$:g" -i po/Makefile.in.in
+run_or_die ${SED} -i.bak -e "s:'\^\$\$lang\$\$':\^\$\$lang\$\$:g" po/Makefile.in.in
run_or_die ${ACLOCAL} ${ACLOCAL_FLAGS:-"-I m4macros"}
run_or_die ${AUTOHEADER} ${AUTOHEADER_FLAGS}
run_or_die ${AUTOMAKE} ${AUTOMAKE_FLAGS:-"-a -c --gnu"}
diff --git a/configure.ac b/configure.ac
index 15cbb56a32..79f479c4d0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -46,7 +46,7 @@ AC_PREREQ([2.50])
m4_define([purple_lt_current], [6])
m4_define([purple_major_version], [2])
m4_define([purple_minor_version], [6])
-m4_define([purple_micro_version], [3])
+m4_define([purple_micro_version], [4])
m4_define([purple_version_suffix], [devel])
m4_define([purple_version],
[purple_major_version.purple_minor_version.purple_micro_version])
@@ -55,7 +55,7 @@ m4_define([purple_display_version], purple_version[]m4_ifdef([purple_version_suf
m4_define([gnt_lt_current], [6])
m4_define([gnt_major_version], [2])
m4_define([gnt_minor_version], [6])
-m4_define([gnt_micro_version], [3])
+m4_define([gnt_micro_version], [4])
m4_define([gnt_version_suffix], [devel])
m4_define([gnt_version],
[gnt_major_version.gnt_minor_version.gnt_micro_version])
@@ -1078,7 +1078,7 @@ if test "x$STATIC_PRPLS" != "x" -a "x$DYNAMIC_PRPLS" = "xall"; then
fi
if test "x$STATIC_PRPLS" = "xall" ; then
- STATIC_PRPLS="bonjour gg irc jabber msn myspace novell oscar qq sametime silc simple yahoo zephyr"
+ STATIC_PRPLS="bonjour gg irc jabber msn myspace mxit novell oscar qq sametime silc simple yahoo zephyr"
fi
if test "x$have_meanwhile" != "xyes" ; then
STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/sametime//'`
@@ -1135,6 +1135,7 @@ for i in $STATIC_PRPLS ; do
msn) static_msn=yes ;;
msnp9) static_msn=yes ;;
myspace) static_myspace=yes ;;
+ mxit) static_mxit=yes ;;
novell) static_novell=yes ;;
oscar) static_oscar=yes ;;
aim) static_oscar=yes ;;
@@ -1155,6 +1156,7 @@ AM_CONDITIONAL(STATIC_IRC, test "x$static_irc" = "xyes")
AM_CONDITIONAL(STATIC_JABBER, test "x$static_jabber" = "xyes")
AM_CONDITIONAL(STATIC_MSN, test "x$static_msn" = "xyes")
AM_CONDITIONAL(STATIC_MYSPACE, test "x$static_myspace" = "xyes")
+AM_CONDITIONAL(STATIC_MXIT, test "x$static_mxit" = "xyes")
AM_CONDITIONAL(STATIC_NOVELL, test "x$static_novell" = "xyes")
AM_CONDITIONAL(STATIC_OSCAR, test "x$static_oscar" = "xyes")
AM_CONDITIONAL(STATIC_QQ, test "x$static_qq" = "xyes")
@@ -1169,7 +1171,7 @@ AC_DEFINE_UNQUOTED(STATIC_PROTO_INIT, $extern_init static void static_proto_init
AC_ARG_WITH(dynamic_prpls, [AC_HELP_STRING([--with-dynamic-prpls], [specify which protocols to build dynamically])], [DYNAMIC_PRPLS=`echo $withval | $sedpath 's/,/ /g'`])
if test "x$DYNAMIC_PRPLS" = "xall" ; then
- DYNAMIC_PRPLS="bonjour gg irc jabber msn myspace novell oscar qq sametime silc simple yahoo zephyr"
+ DYNAMIC_PRPLS="bonjour gg irc jabber msn myspace mxit novell oscar qq sametime silc simple yahoo zephyr"
fi
if test "x$have_meanwhile" != "xyes"; then
DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/sametime//'`
@@ -1196,6 +1198,7 @@ for i in $DYNAMIC_PRPLS ; do
msn) dynamic_msn=yes ;;
msnp9) dynamic_msn=yes ;;
myspace) dynamic_myspace=yes ;;
+ mxit) dynamic_mxit=yes ;;
novell) dynamic_novell=yes ;;
null) dynamic_null=yes ;;
oscar) dynamic_oscar=yes ;;
@@ -1773,6 +1776,23 @@ if test "x$enable_gnutls" = "xyes"; then
LIBS="$LIBS_save"
fi
+if test "x$enable_gnutls" = "xyes"; then
+ AC_MSG_CHECKING(for GNUTLS_CERT_INSECURE_ALGORITHM)
+ LIBS_save="$LIBS"
+ LIBS="$LIBS $GNUTLS_LIBS"
+ CPPFLAGS_save="$CPPFLAGS"
+ CPPFLAGS="$CPPFLAGS $GNUTLS_CFLAGS"
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <gnutls/gnutls.h>]],
+ [[unsigned int verify = GNUTLS_CERT_INSECURE_ALGORITHM;]])],
+ [AC_DEFINE([HAVE_GNUTLS_CERT_INSECURE_ALGORITHM], 1,
+ [Define if your gnutls has the GNUTLS_CERT_INSECURE_ALGORITHM flag])
+ AC_MSG_RESULT(yes)],
+ [AC_MSG_RESULT(no)])
+ CPPFLAGS="$CPPFLAGS_save"
+ LIBS="$LIBS_save"
+fi
+
+
AM_CONDITIONAL(USE_GNUTLS, test "x$enable_gnutls" = "xyes")
@@ -2512,6 +2532,7 @@ AC_OUTPUT([Makefile
libpurple/protocols/msn/Makefile
libpurple/protocols/msnp9/Makefile
libpurple/protocols/myspace/Makefile
+ libpurple/protocols/mxit/Makefile
libpurple/protocols/novell/Makefile
libpurple/protocols/null/Makefile
libpurple/protocols/oscar/Makefile
diff --git a/doc/pidgin.1.in b/doc/pidgin.1.in
index dba79a75ad..81c7fea28a 100644
--- a/doc/pidgin.1.in
+++ b/doc/pidgin.1.in
@@ -628,6 +628,8 @@ Pidgin's active developers are:
.br
Richard 'rlaager' Laager (developer) <\fIrlaager@pidgin.im\fR>
.br
+ Sulabh 'sulabh_m' Mahajan (developer)
+.br
Richard 'wabz' Nelson (developer)
.br
Christopher 'siege' O'Brien (developer)
diff --git a/finch/gntblist.c b/finch/gntblist.c
index fad3d27809..f4e48290a2 100644
--- a/finch/gntblist.c
+++ b/finch/gntblist.c
@@ -1940,7 +1940,7 @@ key_pressed(GntWidget *widget, const char *text, FinchBlist *ggblist)
} else if (!gnt_tree_is_searching(GNT_TREE(ggblist->tree))) {
if (strcmp(text, "t") == 0) {
finch_blist_toggle_tag_buddy(gnt_tree_get_selection_data(GNT_TREE(ggblist->tree)));
- gnt_bindable_perform_action_named(GNT_BINDABLE(ggblist->tree), "move-down");
+ gnt_bindable_perform_action_named(GNT_BINDABLE(ggblist->tree), "move-down", NULL);
} else if (strcmp(text, "a") == 0) {
finch_blist_place_tagged(gnt_tree_get_selection_data(GNT_TREE(ggblist->tree)));
} else
diff --git a/finch/gntconn.c b/finch/gntconn.c
index b23bb6a8d6..d66b9a284b 100644
--- a/finch/gntconn.c
+++ b/finch/gntconn.c
@@ -107,7 +107,6 @@ finch_connection_report_disconnect(PurpleConnection *gc, PurpleConnectionError r
{
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 @@ finch_connection_report_disconnect(PurpleConnection *gc, PurpleConnectionError r
g_free(secondary);
purple_account_set_enabled(account, FINCH_UI, FALSE);
}
-
- /* If we have any open chats, we probably want to rejoin when we get back online. */
- list = purple_get_chats();
- while (list) {
- PurpleConversation *conv = list->data;
- list = list->next;
- if (purple_conversation_get_account(conv) != account ||
- purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv)))
- continue;
- purple_conversation_set_data(conv, "want-to-rejoin", GINT_TO_POINTER(TRUE));
- purple_conversation_write(conv, NULL, _("The account has disconnected and you are no "
- "longer in this chat. You will be automatically rejoined in the chat when "
- "the account reconnects."),
- PURPLE_MESSAGE_SYSTEM, time(NULL));
- }
}
static void
diff --git a/finch/gntconv.c b/finch/gntconv.c
index 51996d640b..254bcc3fe2 100644
--- a/finch/gntconv.c
+++ b/finch/gntconv.c
@@ -367,6 +367,28 @@ account_signed_on_off(PurpleConnection *gc, gpointer null)
}
}
+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 @@ static void
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 @@ void finch_conversation_init()
PURPLE_CALLBACK(account_signed_on_off), NULL);
purple_signal_connect(purple_connections_get_handle(), "signed-off", finch_conv_get_handle(),
PURPLE_CALLBACK(account_signed_on_off), NULL);
+ purple_signal_connect(purple_connections_get_handle(), "signing-off", finch_conv_get_handle(),
+ PURPLE_CALLBACK(account_signing_off), NULL);
}
void finch_conversation_uninit()
diff --git a/finch/libgnt/gntbindable.h b/finch/libgnt/gntbindable.h
index 418ea41d25..04d806d2ec 100644
--- a/finch/libgnt/gntbindable.h
+++ b/finch/libgnt/gntbindable.h
@@ -166,7 +166,7 @@ gboolean gnt_bindable_check_key(GntBindable *bindable, const char *keys);
*
* @return @c TRUE if the action was performed successfully, @c FALSE otherwise.
*/
-gboolean gnt_bindable_perform_action_named(GntBindable *bindable, const char *name, ...);
+gboolean gnt_bindable_perform_action_named(GntBindable *bindable, const char *name, ...) G_GNUC_NULL_TERMINATED;
/**
* Returns a GntTree populated with "key" -> "binding" for the widget.
diff --git a/finch/libgnt/gntbox.c b/finch/libgnt/gntbox.c
index 7fb38e6738..8b1b29cb66 100644
--- a/finch/libgnt/gntbox.c
+++ b/finch/libgnt/gntbox.c
@@ -687,8 +687,8 @@ void gnt_box_set_title(GntBox *b, const char *title)
get_title_thingies(b, prev, &pos, &right);
mvwhline(w->window, 0, pos - 1, ACS_HLINE | gnt_color_pair(GNT_COLOR_NORMAL),
right - pos + 2);
- g_free(prev);
}
+ g_free(prev);
}
void gnt_box_set_pad(GntBox *box, int pad)
diff --git a/finch/libgnt/gntcolors.c b/finch/libgnt/gntcolors.c
index c8bfa0929b..ceaf895c95 100644
--- a/finch/libgnt/gntcolors.c
+++ b/finch/libgnt/gntcolors.c
@@ -208,8 +208,10 @@ void gnt_colors_parse(GKeyFile *kfile)
key = g_ascii_strdown(key, -1);
color = gnt_colors_get_color(key);
g_free(key);
- if (color == -EINVAL)
+ if (color == -EINVAL) {
+ g_strfreev(list);
continue;
+ }
init_color(color, r, g, b);
}
@@ -251,8 +253,10 @@ void gnt_color_pairs_parse(GKeyFile *kfile)
int bg = gnt_colors_get_color(bgc);
g_free(fgc);
g_free(bgc);
- if (fg == -EINVAL || bg == -EINVAL)
+ if (fg == -EINVAL || bg == -EINVAL) {
+ g_strfreev(list);
continue;
+ }
key = g_ascii_strdown(key, -1);
@@ -275,6 +279,7 @@ void gnt_color_pairs_parse(GKeyFile *kfile)
else if (strcmp(key, "urgent") == 0)
type = GNT_COLOR_URGENT;
else {
+ g_strfreev(list);
g_free(key);
continue;
}
diff --git a/finch/libgnt/gntentry.c b/finch/libgnt/gntentry.c
index 85e06f351d..eb42054b0c 100644
--- a/finch/libgnt/gntentry.c
+++ b/finch/libgnt/gntentry.c
@@ -495,7 +495,7 @@ suggest_show(GntBindable *bind, GList *null)
{
GntEntry *entry = GNT_ENTRY(bind);
if (entry->ddown) {
- gnt_bindable_perform_action_named(GNT_BINDABLE(entry->ddown), "move-down");
+ gnt_bindable_perform_action_named(GNT_BINDABLE(entry->ddown), "move-down", NULL);
return TRUE;
}
return show_suggest_dropdown(entry);
@@ -1044,8 +1044,11 @@ gnt_entry_set_text_internal(GntEntry *entry, const char *text)
snprintf(entry->start, len + 1, "%s", text);
entry->end = entry->start + len;
- entry->scroll = entry->start + scroll;
- entry->cursor = entry->end - cursor;
+ if ((entry->scroll = entry->start + scroll) > entry->end)
+ entry->scroll = entry->end;
+
+ if ((entry->cursor = entry->end - cursor) > entry->end)
+ entry->cursor = entry->end;
if (GNT_WIDGET_IS_FLAG_SET(GNT_WIDGET(entry), GNT_WIDGET_MAPPED))
entry_redraw(GNT_WIDGET(entry));
diff --git a/finch/libgnt/gntfilesel.c b/finch/libgnt/gntfilesel.c
index 7187315667..abf620a403 100644
--- a/finch/libgnt/gntfilesel.c
+++ b/finch/libgnt/gntfilesel.c
@@ -176,9 +176,13 @@ process_path(const char *path)
splits = g_strsplit(path, G_DIR_SEPARATOR_S, -1);
for (i = 0, j = 0; splits[i]; i++) {
if (strcmp(splits[i], ".") == 0) {
+ g_free(splits[i]);
+ splits[i] = NULL;
} else if (strcmp(splits[i], "..") == 0) {
if (j)
j--;
+ g_free(splits[i]);
+ splits[i] = NULL;
} else {
if (i != j) {
g_free(splits[j]);
@@ -625,6 +629,7 @@ gnt_file_sel_init(GTypeInstance *instance, gpointer class)
sel->files = gnt_tree_new_with_columns(2); /* Name, Size */
gnt_tree_set_compare_func(GNT_TREE(sel->files), (GCompareFunc)g_utf8_collate);
+ gnt_tree_set_hash_fns(GNT_TREE(sel->files), g_str_hash, g_str_equal, g_free);
gnt_tree_set_column_titles(GNT_TREE(sel->files), "Filename", "Size");
gnt_tree_set_show_title(GNT_TREE(sel->files), TRUE);
gnt_tree_set_col_width(GNT_TREE(sel->files), 0, 25);
diff --git a/finch/libgnt/gnttextview.c b/finch/libgnt/gnttextview.c
index 1a1354c261..5a7c7f8c70 100644
--- a/finch/libgnt/gnttextview.c
+++ b/finch/libgnt/gnttextview.c
@@ -67,6 +67,12 @@ static gboolean double_click;
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 @@ gnt_text_view_draw(GntWidget *widget)
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 @@ gnt_text_view_clicked(GntWidget *widget, GntMouseEvent event, int x, int y)
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 @@ gnt_text_view_clicked(GntWidget *widget, GntMouseEvent event, int x, int y)
}
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;
@@ -767,6 +774,7 @@ int gnt_text_view_tag_change(GntTextView *view, const char *name, const char *te
line->segments = g_list_delete_link(line->segments, segs);
if (line->segments == NULL) {
free_text_line(line, NULL);
+ line = NULL;
if (view->list == iter) {
if (inext)
view->list = inext;
@@ -780,7 +788,8 @@ int gnt_text_view_tag_change(GntTextView *view, const char *name, const char *te
seg->start = tag->start;
seg->end = tag->end - change;
}
- line->length -= change;
+ if (line)
+ line->length -= change;
/* XXX: Make things work if the tagged text spans over several lines. */
} else {
/* XXX: handle the rest of the conditions */
diff --git a/finch/libgnt/gnttree.c b/finch/libgnt/gnttree.c
index 6bb495629b..6f03c3b526 100644
--- a/finch/libgnt/gnttree.c
+++ b/finch/libgnt/gnttree.c
@@ -815,7 +815,7 @@ gnt_tree_key_pressed(GntWidget *widget, const char *text)
gnt_widget_activate(widget);
} else if (tree->priv->search) {
gboolean changed = TRUE;
- if (isalnum(*text)) {
+ if (g_unichar_isprint(*text)) {
tree->priv->search = g_string_append_c(tree->priv->search, *text);
} else if (g_utf8_collate(text, GNT_KEY_BACKSPACE) == 0) {
if (tree->priv->search->len)
diff --git a/finch/libgnt/gntutils.c b/finch/libgnt/gntutils.c
index c6712a15dc..171e2c04fb 100644
--- a/finch/libgnt/gntutils.c
+++ b/finch/libgnt/gntutils.c
@@ -374,6 +374,7 @@ void gnt_util_parse_widgets(const char *string, int num, ...)
gnt_widget_from_xmlnode(node, data, num);
xmlFreeDoc(doc);
+ xmlFreeParserCtxt(ctxt);
xmlCleanupParser();
va_end(list);
g_free(data);
@@ -470,6 +471,7 @@ gboolean gnt_util_parse_xhtml_to_textview(const char *string, GntTextView *tv)
xmlFreeDoc(doc);
ret = TRUE;
}
+ xmlFreeParserCtxt(ctxt);
xmlCleanupParser();
return ret;
#endif
diff --git a/finch/plugins/gnttinyurl.c b/finch/plugins/gnttinyurl.c
index 553d3120df..4d70de1d03 100644
--- a/finch/plugins/gnttinyurl.c
+++ b/finch/plugins/gnttinyurl.c
@@ -41,7 +41,10 @@
#include <gntconv.h>
#include <gntplugin.h>
+
+#include <gntlabel.h>
#include <gnttextview.h>
+#include <gntwindow.h>
static int tag_num = 0;
@@ -52,6 +55,8 @@ typedef struct
int num;
} CbInfo;
+static void process_urls(PurpleConversation *conv, GList *urls);
+
/* 3 functions from util.c */
static gboolean
badchar(char c)
@@ -83,7 +88,8 @@ badentity(const char *c)
return FALSE;
}
-static GList *extract_urls(char *text) {
+static GList *extract_urls(const char *text)
+{
const char *t, *c, *q = NULL;
char *url_buf;
GList *ret = NULL;
@@ -142,7 +148,9 @@ static GList *extract_urls(char *text) {
url_buf = g_strndup(c, t - c);
if (!g_list_find_custom(ret, url_buf, (GCompareFunc)strcmp)) {
purple_debug_info("TinyURL", "Added URL %s\n", url_buf);
- ret = g_list_append(ret, g_strdup(url_buf));
+ ret = g_list_append(ret, url_buf);
+ } else {
+ g_free(url_buf);
}
c = t;
break;
@@ -173,6 +181,8 @@ static GList *extract_urls(char *text) {
if (!g_list_find_custom(ret, url_buf, (GCompareFunc)strcmp)) {
purple_debug_info("TinyURL", "Added URL %s\n", url_buf);
ret = g_list_append(ret, url_buf);
+ } else {
+ g_free(url_buf);
}
c = t;
break;
@@ -207,10 +217,12 @@ static void url_fetched(PurpleUtilFetchUrlData *url_data, gpointer cb_data,
gnt_text_view_tag_change(tv, data->tag, str, FALSE);
g_free(str);
g_free(data->tag);
+ g_free(data);
return;
}
}
g_free(data->tag);
+ g_free(data);
purple_debug_info("TinyURL", "Conversation no longer exists... :(\n");
}
@@ -219,13 +231,14 @@ static void free_urls(gpointer data, gpointer null)
g_free(data);
}
-static gboolean receiving_msg(PurpleAccount *account, char **sender, char **message,
- PurpleConversation *conv, PurpleMessageFlags *flags) {
+static gboolean writing_msg(PurpleAccount *account, char *sender, char **message,
+ PurpleConversation *conv, PurpleMessageFlags flags)
+{
GString *t;
- GList *iter, *urls;
+ GList *iter, *urls, *next;
int c = 0;
- if (!(*flags & PURPLE_MESSAGE_RECV) || *flags & PURPLE_MESSAGE_INVISIBLE)
+ if ((flags & (PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_INVISIBLE)))
return FALSE;
urls = purple_conversation_get_data(conv, "TinyURLs");
@@ -238,7 +251,8 @@ static gboolean receiving_msg(PurpleAccount *account, char **sender, char **mess
t = g_string_new(*message);
g_free(*message);
- for (iter = urls; iter; iter = iter->next) {
+ for (iter = urls; iter; iter = next) {
+ next = iter->next;
if (g_utf8_strlen((char *)iter->data, -1) >= purple_prefs_get_int(PREF_LENGTH)) {
int pos, x = 0;
gchar *j, *s, *str, *orig;
@@ -256,36 +270,40 @@ static gboolean receiving_msg(PurpleAccount *account, char **sender, char **mess
g_free(str);
continue;
} else {
- if (iter->prev) {
- iter = iter->prev;
- g_free(iter->next->data);
- urls = g_list_delete_link(urls, iter->next);
- } else {
- g_free(iter->data);
- g_list_free(urls);
- urls = NULL;
- }
+ g_free(iter->data);
+ urls = g_list_delete_link(urls, iter);
}
}
*message = t->str;
g_string_free(t, FALSE);
if (conv == NULL)
- conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, *sender);
+ conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, sender);
purple_conversation_set_data(conv, "TinyURLs", urls);
return FALSE;
}
-static void received_msg(PurpleAccount *account, char *sender, char *message,
- PurpleConversation *conv, PurpleMessageFlags flags) {
- int c;
- GList *urls, *iter;
- FinchConv *fconv = FINCH_CONV(conv);
- GntTextView *tv = GNT_TEXT_VIEW(fconv->tv);
+static void wrote_msg(PurpleAccount *account, char *sender, char *message,
+ PurpleConversation *conv, PurpleMessageFlags flags)
+{
+ GList *urls;
urls = purple_conversation_get_data(conv, "TinyURLs");
- if (!(flags & PURPLE_MESSAGE_RECV) || urls == NULL)
+ if ((flags & PURPLE_MESSAGE_SEND) || urls == NULL)
return;
+ process_urls(conv, urls);
+ purple_conversation_set_data(conv, "TinyURLs", NULL);
+}
+
+/* Frees 'urls' */
+static void
+process_urls(PurpleConversation *conv, GList *urls)
+{
+ GList *iter;
+ int c;
+ FinchConv *fconv = FINCH_CONV(conv);
+ GntTextView *tv = GNT_TEXT_VIEW(fconv->tv);
+
for (iter = urls, c = 0; iter; iter = iter->next) {
int i;
CbInfo *cbdata;
@@ -312,7 +330,6 @@ static void received_msg(PurpleAccount *account, char *sender, char *message,
g_free(url);
}
g_list_free(urls);
- purple_conversation_set_data(conv, "TinyURLs", NULL);
}
static void
@@ -324,20 +341,75 @@ free_conv_urls(PurpleConversation *conv)
g_list_free(urls);
}
+static void tinyurl_notify_fetch_cb(PurpleUtilFetchUrlData *urldata, gpointer cbdata,
+ const gchar *urltext, gsize len, const gchar *error)
+{
+ GntWidget *win = cbdata;
+ GntWidget *label = g_object_get_data(G_OBJECT(win), "info-widget");
+ char *message;
+
+ message = g_strdup_printf(_("TinyURL for above: %s"), urltext);
+ gnt_label_set_text(GNT_LABEL(label), message);
+ g_free(message);
+
+ g_signal_handlers_disconnect_matched(G_OBJECT(win), G_SIGNAL_MATCH_FUNC,
+ 0, 0, NULL,
+ G_CALLBACK(purple_util_fetch_url_cancel), NULL);
+}
+
+static void *
+tinyurl_notify_uri(const char *uri)
+{
+ char *fullurl = NULL;
+ GntWidget *win;
+ PurpleUtilFetchUrlData *urlcb;
+
+ /* 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. */
+ win = purple_notify_message(NULL, PURPLE_NOTIFY_URI, _("URI"), uri,
+ _("Please wait while TinyURL fetches a shorter URL ..."), NULL, NULL);
+ if (!GNT_IS_WINDOW(win) || !g_object_get_data(G_OBJECT(win), "info-widget"))
+ return win;
+
+ if (g_ascii_strncasecmp(uri, "http://", 7) && g_ascii_strncasecmp(uri, "https://", 8)) {
+ fullurl = g_strdup_printf("%shttp%%3A%%2F%%2F%s",
+ purple_prefs_get_string(PREF_URL), purple_url_encode(uri));
+ } else {
+ fullurl = g_strdup_printf("%s%s", purple_prefs_get_string(PREF_URL),
+ purple_url_encode(uri));
+ }
+
+ /* Store the return value of _fetch_url and destroy that when win is
+ destroyed, so that the callback for _fetch_url does not try to molest a
+ non-existent window */
+ urlcb = purple_util_fetch_url(fullurl, TRUE, "finch", FALSE, tinyurl_notify_fetch_cb, win);
+ g_free(fullurl);
+ g_signal_connect_swapped(G_OBJECT(win), "destroy",
+ G_CALLBACK(purple_util_fetch_url_cancel), urlcb);
+
+ return win;
+}
+
static gboolean
-plugin_load(PurplePlugin *plugin) {
+plugin_load(PurplePlugin *plugin)
+{
+ PurpleNotifyUiOps *ops = purple_notify_get_ui_ops();
+ plugin->extra = ops->notify_uri;
+ ops->notify_uri = tinyurl_notify_uri;
+
purple_signal_connect(purple_conversations_get_handle(),
"wrote-im-msg",
- plugin, PURPLE_CALLBACK(received_msg), NULL);
+ plugin, PURPLE_CALLBACK(wrote_msg), NULL);
purple_signal_connect(purple_conversations_get_handle(),
"wrote-chat-msg",
- plugin, PURPLE_CALLBACK(received_msg), NULL);
+ plugin, PURPLE_CALLBACK(wrote_msg), NULL);
purple_signal_connect(purple_conversations_get_handle(),
- "receiving-im-msg",
- plugin, PURPLE_CALLBACK(receiving_msg), NULL);
+ "writing-im-msg",
+ plugin, PURPLE_CALLBACK(writing_msg), NULL);
purple_signal_connect(purple_conversations_get_handle(),
- "receiving-chat-msg",
- plugin, PURPLE_CALLBACK(receiving_msg), NULL);
+ "writing-chat-msg",
+ plugin, PURPLE_CALLBACK(writing_msg), NULL);
purple_signal_connect(purple_conversations_get_handle(),
"deleting-conversation",
plugin, PURPLE_CALLBACK(free_conv_urls), NULL);
@@ -345,6 +417,15 @@ plugin_load(PurplePlugin *plugin) {
return TRUE;
}
+static gboolean
+plugin_unload(PurplePlugin *plugin)
+{
+ PurpleNotifyUiOps *ops = purple_notify_get_ui_ops();
+ if (ops->notify_uri == tinyurl_notify_uri)
+ ops->notify_uri = plugin->extra;
+ return TRUE;
+}
+
static PurplePluginPrefFrame *
get_plugin_pref_frame(PurplePlugin *plugin) {
@@ -394,7 +475,7 @@ static PurplePluginInfo info =
"Richard Nelson <wabz@whatsbeef.net>",
PURPLE_WEBSITE,
plugin_load,
- NULL,
+ plugin_unload,
NULL,
NULL,
NULL,
diff --git a/libpurple/Makefile.am b/libpurple/Makefile.am
index 01f42bec74..efb8d32c66 100644
--- a/libpurple/Makefile.am
+++ b/libpurple/Makefile.am
@@ -32,7 +32,7 @@ endif
pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = purple.pc
-SUBDIRS = $(GCONF_DIR) plugins protocols tests . example
+SUBDIRS = $(GCONF_DIR) plugins protocols . tests example
purple_coresources = \
account.c \
diff --git a/libpurple/account.c b/libpurple/account.c
index af2397ea38..78100bbb99 100644
--- a/libpurple/account.c
+++ b/libpurple/account.c
@@ -1050,9 +1050,22 @@ purple_account_destroy(PurpleAccount *account)
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);
- g_free(priv->current_error);
+ if (priv->current_error) {
+ g_free(priv->current_error->description);
+ g_free(priv->current_error);
+ }
g_free(priv);
PURPLE_DBUS_UNREGISTER_POINTER(account);
diff --git a/libpurple/blist.c b/libpurple/blist.c
index 09faeb676d..6749cab811 100644
--- a/libpurple/blist.c
+++ b/libpurple/blist.c
@@ -2009,17 +2009,13 @@ void purple_blist_add_group(PurpleGroup *group, PurpleBlistNode *node)
ops = purple_blist_get_ui_ops();
- if (!purplebuddylist->root) {
- purplebuddylist->root = gnode;
-
- key = g_utf8_collate_key(group->name, -1);
- g_hash_table_insert(groups_cache, key, group);
- return;
- }
-
/* if we're moving to overtop of ourselves, do nothing */
- if (gnode == node)
- return;
+ if (gnode == node) {
+ if (!purplebuddylist->root)
+ node = NULL;
+ else
+ return;
+ }
if (purple_find_group(group->name)) {
/* This is just being moved */
diff --git a/libpurple/certificate.c b/libpurple/certificate.c
index 8c1f85d067..2b065b4b77 100644
--- a/libpurple/certificate.c
+++ b/libpurple/certificate.c
@@ -97,8 +97,8 @@ invalidity_reason_to_string(PurpleCertificateInvalidityFlags flag)
"automatically checked.");
break;
case PURPLE_CERTIFICATE_CA_UNKNOWN:
- return _("The root certificate this one claims to be issued by is "
- "unknown.");
+ return _("The certificate is not trusted because no certificate "
+ "that can verify it is currently trusted.");
break;
case PURPLE_CERTIFICATE_NOT_ACTIVATED:
return _("The certificate is not valid yet.");
@@ -1402,13 +1402,15 @@ x509_tls_cached_complete(PurpleCertificateVerificationRequest *vrq,
if (flags & PURPLE_CERTIFICATE_NAME_MISMATCH) {
gchar *sn = purple_certificate_get_subject_name(peer_crt);
- g_string_append_printf(errors, _("The certificate claims to be "
- "from \"%s\" instead. This could mean that you are "
- "not connecting to the service you believe you are."),
- sn);
- g_free(sn);
+ if (sn) {
+ g_string_append_printf(errors, _("The certificate claims to be "
+ "from \"%s\" instead. This could mean that you are "
+ "not connecting to the service you believe you are."),
+ sn);
+ g_free(sn);
- flags &= ~PURPLE_CERTIFICATE_NAME_MISMATCH;
+ flags &= ~PURPLE_CERTIFICATE_NAME_MISMATCH;
+ }
}
while (i != PURPLE_CERTIFICATE_LAST) {
diff --git a/libpurple/cipher.c b/libpurple/cipher.c
index c82865f952..cc5898ea90 100644
--- a/libpurple/cipher.c
+++ b/libpurple/cipher.c
@@ -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"
diff --git a/libpurple/dnsquery.c b/libpurple/dnsquery.c
index baf7a18730..4e0ed4687d 100644
--- a/libpurple/dnsquery.c
+++ b/libpurple/dnsquery.c
@@ -172,6 +172,7 @@ resolve_ip(PurpleDnsQueryData *query_data)
return FALSE;
}
+#ifdef USE_IDN
static gboolean
dns_str_is_ascii(const char *name)
{
@@ -183,6 +184,7 @@ dns_str_is_ascii(const char *name)
return TRUE;
}
+#endif
#if defined(PURPLE_DNSQUERY_USE_FORK)
@@ -293,12 +295,11 @@ purple_dnsquery_resolver_run(int child_out, int child_in, gboolean show_debug)
rc = purple_network_convert_idn_to_ascii(dns_params.hostname, &hostname);
if (rc != 0) {
write_to_parent(child_out, &rc, sizeof(rc));
- close(child_out);
if (show_debug)
fprintf(stderr, "dns[%d] Error: IDN conversion returned "
"%d\n", getpid(), rc);
dns_params.hostname[0] = '\0';
- continue;
+ break;
}
} else /* intentional to execute the g_strdup */
#endif
@@ -323,14 +324,13 @@ purple_dnsquery_resolver_run(int child_out, int child_in, gboolean show_debug)
rc = getaddrinfo(hostname, servname, &hints, &res);
write_to_parent(child_out, &rc, sizeof(rc));
if (rc != 0) {
- close(child_out);
if (show_debug)
printf("dns[%d] Error: getaddrinfo returned %d\n",
getpid(), rc);
dns_params.hostname[0] = '\0';
g_free(hostname);
hostname = NULL;
- continue;
+ break;
}
tmp = res;
while (res) {
diff --git a/libpurple/dnsquery.h b/libpurple/dnsquery.h
index f03ac23b64..d68286414c 100644
--- a/libpurple/dnsquery.h
+++ b/libpurple/dnsquery.h
@@ -30,6 +30,11 @@
#include "eventloop.h"
#include "account.h"
+/**
+ * An opaque structure representing a DNS query. The hostname and port
+ * associated with the query can be retrieved using
+ * purple_dnsquery_get_host() and purple_dnsquery_get_port().
+ */
typedef struct _PurpleDnsQueryData PurpleDnsQueryData;
/**
diff --git a/libpurple/dnssrv.c b/libpurple/dnssrv.c
index 82b1f0deaa..088336253e 100644
--- a/libpurple/dnssrv.c
+++ b/libpurple/dnssrv.c
@@ -248,6 +248,7 @@ purple_srv_sort(GList *list)
return list;
}
+#ifdef USE_IDN
static gboolean
dns_str_is_ascii(const char *name)
{
@@ -259,8 +260,60 @@ dns_str_is_ascii(const char *name)
return TRUE;
}
+#endif
#ifndef _WIN32
+static void
+write_to_parent(int in, int out, gconstpointer data, gsize size)
+{
+ const guchar *buf = data;
+ gssize w;
+
+ do {
+ w = write(out, buf, size);
+ if (w > 0) {
+ buf += w;
+ size -= w;
+ } else if (w < 0 && errno == EINTR) {
+ /* Let's try some more; */
+ w = 1;
+ }
+ } while (size > 0 && w > 0);
+
+ if (size != 0) {
+ /* An error occurred */
+ close(out);
+ close(in);
+ _exit(0);
+ }
+}
+
+/* Read size bytes to data. Dies if an error occurs. */
+static void
+read_from_parent(int in, int out, gpointer data, gsize size)
+{
+ guchar *buf = data;
+ gssize r;
+
+ do {
+ r = read(in, data, size);
+ if (r > 0) {
+ buf += r;
+ size -= r;
+ } else if (r < 0 && errno == EINTR) {
+ /* Let's try some more; */
+ r = 1;
+ }
+ } while (size > 0 && r > 0);
+
+ if (size != 0) {
+ /* An error occurred */
+ close(out);
+ close(in);
+ _exit(0);
+ }
+}
+
G_GNUC_NORETURN static void
resolve(int in, int out)
@@ -279,16 +332,12 @@ resolve(int in, int out)
purple_restore_default_signal_handlers();
#endif
- if (read(in, &query, sizeof(query)) <= 0) {
- close(out);
- close(in);
- _exit(0);
- }
+ read_from_parent(in, out, &query, sizeof(query));
size = res_query( query.query, C_IN, query.type, (u_char*)&answer, sizeof( answer));
if (size == -1) {
- write(out, &(query.type), sizeof(query.type));
- write(out, &size, sizeof(int));
+ write_to_parent(in, out, &(query.type), sizeof(query.type));
+ write_to_parent(in, out, &size, sizeof(size));
close(out);
close(in);
_exit(0);
@@ -353,16 +402,18 @@ end:
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);
@@ -429,21 +480,38 @@ resolved(gpointer data, gint source, PurpleInputCondition cond)
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 length: %s\n", g_strerror(errno));
+ size = 0;
+ 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;
- g_free(res);
+ 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);
@@ -674,6 +742,7 @@ purple_srv_resolve(const char *protocol, const char *transport, const char *doma
internal_query.type = T_SRV;
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 SRV resolver\n");
@@ -787,6 +856,7 @@ PurpleSrvQueryData *purple_txt_resolve(const char *owner, const char *domain, Pu
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");
diff --git a/libpurple/ft.c b/libpurple/ft.c
index f1ee489a28..2b07e537e7 100644
--- a/libpurple/ft.c
+++ b/libpurple/ft.c
@@ -1030,7 +1030,7 @@ do_transfer(PurpleXfer *xfer)
* watcher.
*/
if (xfer->watcher != 0) {
- purple_timeout_remove(xfer->watcher);
+ purple_input_remove(xfer->watcher);
xfer->watcher = 0;
}
diff --git a/libpurple/ft.h b/libpurple/ft.h
index ad5526602c..f4327a50c6 100644
--- a/libpurple/ft.h
+++ b/libpurple/ft.h
@@ -674,7 +674,7 @@ void purple_xfer_conversation_write(PurpleXfer *xfer, char *message, gboolean is
void purple_xfer_ui_ready(PurpleXfer *xfer);
/**
- * Allows the prpl to signal it's readh to send/receive data (depending on
+ * Allows the prpl to signal it's ready to send/receive data (depending on
* the direction of the file transfer. Used when the prpl provides read/write
* ops and cannot/does not provide a raw fd to the core.
*
diff --git a/libpurple/media.c b/libpurple/media.c
index 0d0e33f805..142da3c7cd 100644
--- a/libpurple/media.c
+++ b/libpurple/media.c
@@ -103,6 +103,8 @@ struct _PurpleMediaStream
gboolean initiator;
gboolean accepted;
gboolean candidates_prepared;
+ gboolean held;
+ gboolean paused;
GList *active_local_candidates;
GList *active_remote_candidates;
@@ -281,7 +283,7 @@ purple_media_info_type_get_type()
{ PURPLE_MEDIA_INFO_HOLD,
"PURPLE_MEDIA_INFO_HOLD", "hold" },
{ PURPLE_MEDIA_INFO_UNHOLD,
- "PURPLE_MEDIA_INFO_HOLD", "unhold" },
+ "PURPLE_MEDIA_INFO_UNHOLD", "unhold" },
{ 0, NULL, NULL }
};
type = g_enum_register_static("PurpleMediaInfoType", values);
@@ -2330,11 +2332,46 @@ purple_media_stream_info(PurpleMedia *media, PurpleMediaInfoType type,
for (; streams; streams = g_list_delete_link(streams, streams)) {
PurpleMediaStream *stream = streams->data;
if (stream->session->type & PURPLE_MEDIA_SEND_VIDEO) {
+ stream->paused = active;
+
+ if (!stream->held)
+ g_object_set(stream->stream, "direction",
+ purple_media_to_fs_stream_direction(
+ stream->session->type & ((active) ?
+ ~PURPLE_MEDIA_SEND_VIDEO :
+ PURPLE_MEDIA_VIDEO)), NULL);
+ }
+ }
+ } else if (local == TRUE && (type == PURPLE_MEDIA_INFO_HOLD ||
+ type == PURPLE_MEDIA_INFO_UNHOLD)) {
+ GList *streams;
+ gboolean active = (type == PURPLE_MEDIA_INFO_HOLD);
+
+ g_return_if_fail(PURPLE_IS_MEDIA(media));
+
+ streams = purple_media_get_streams(media,
+ session_id, participant);
+ for (; streams; streams = g_list_delete_link(streams, streams)) {
+ PurpleMediaStream *stream = streams->data;
+ stream->held = active;
+ if (stream->session->type & PURPLE_MEDIA_VIDEO) {
+ FsStreamDirection direction;
+
+ direction = ((active) ?
+ ~PURPLE_MEDIA_VIDEO :
+ PURPLE_MEDIA_VIDEO);
+ if (!active && stream->paused)
+ direction &= ~PURPLE_MEDIA_SEND_VIDEO;
+
+ g_object_set(stream->stream, "direction",
+ purple_media_to_fs_stream_direction(
+ stream->session->type & direction), NULL);
+ } else if (stream->session->type & PURPLE_MEDIA_AUDIO) {
g_object_set(stream->stream, "direction",
purple_media_to_fs_stream_direction(
stream->session->type & ((active) ?
- ~PURPLE_MEDIA_SEND_VIDEO :
- PURPLE_MEDIA_VIDEO)), NULL);
+ ~PURPLE_MEDIA_AUDIO :
+ PURPLE_MEDIA_AUDIO)), NULL);
}
}
}
diff --git a/libpurple/plugins/perl/Makefile.mingw b/libpurple/plugins/perl/Makefile.mingw
index 1768f5d2f3..1556d8ada4 100755
--- a/libpurple/plugins/perl/Makefile.mingw
+++ b/libpurple/plugins/perl/Makefile.mingw
@@ -7,10 +7,12 @@
PIDGIN_TREE_TOP := ../../..
include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
+DEFINES := $(subst -DWIN32_LEAN_AND_MEAN,,$(DEFINES))
+
TARGET = perl
# Perl headers with /* /* */ type comments.. Turn off warnings.
-CFLAGS += -Wno-comment
+GCCWARNINGS += -Wno-comment
##
## INCLUDE PATHS
diff --git a/libpurple/plugins/perl/common/Makefile.mingw b/libpurple/plugins/perl/common/Makefile.mingw
index 03a41d21f5..dbc7b0e872 100644
--- a/libpurple/plugins/perl/common/Makefile.mingw
+++ b/libpurple/plugins/perl/common/Makefile.mingw
@@ -5,9 +5,12 @@
#
PIDGIN_TREE_TOP := ../../../..
-GCCWARNINGS := -Wno-comment -Waggregate-return -Wcast-align -Wdeclaration-after-statement -Werror-implicit-function-declaration -Wextra -Wno-sign-compare -Wno-unused-parameter -Winit-self -Wmissing-declarations -Wmissing-prototypes -Wpointer-arith -Wundef -Wno-unused
include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
+GCCWARNINGS += -Wno-comment -Wno-unused -Wno-nested-externs
+
+DEFINES := $(subst -DWIN32_LEAN_AND_MEAN,,$(DEFINES))
+
TARGET = Purple
AUTOSPLIT = lib/auto/Purple/autosplit.ix
EXTUTILS ?= C:/perl/lib/ExtUtils
diff --git a/libpurple/plugins/perl/common/Prpl.xs b/libpurple/plugins/perl/common/Prpl.xs
index ec535ac806..765ce5d41a 100644
--- a/libpurple/plugins/perl/common/Prpl.xs
+++ b/libpurple/plugins/perl/common/Prpl.xs
@@ -62,11 +62,15 @@ purple_prpl_send_raw(gc, str)
PREINIT:
PurplePluginProtocolInfo *prpl_info;
CODE:
- prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
- if (prpl_info && prpl_info->send_raw != NULL) {
- RETVAL = prpl_info->send_raw(gc, str, strlen(str));
- } else {
+ if (!gc)
RETVAL = 0;
+ else {
+ prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
+ if (prpl_info && prpl_info->send_raw != NULL) {
+ RETVAL = prpl_info->send_raw(gc, str, strlen(str));
+ } else {
+ RETVAL = 0;
+ }
}
OUTPUT:
RETVAL
diff --git a/libpurple/plugins/perl/perl-handlers.c b/libpurple/plugins/perl/perl-handlers.c
index 4326bc5b42..73869a5513 100644
--- a/libpurple/plugins/perl/perl-handlers.c
+++ b/libpurple/plugins/perl/perl-handlers.c
@@ -649,6 +649,7 @@ purple_perl_cmd_register(PurplePlugin *plugin, const gchar *command,
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 @@ purple_perl_cmd_unregister(PurpleCmdId id)
return;
}
- purple_cmd_unregister(id);
destroy_cmd_handler(handler);
}
diff --git a/libpurple/plugins/perl/perl.c b/libpurple/plugins/perl/perl.c
index 89942e9a8d..7fbafb65a4 100644
--- a/libpurple/plugins/perl/perl.c
+++ b/libpurple/plugins/perl/perl.c
@@ -621,6 +621,9 @@ destroy_perl_plugin(PurplePlugin *plugin)
g_free(gps);
plugin->info->extra_info = NULL;
}
+
+ g_free(plugin->info);
+ plugin->info = NULL;
}
}
diff --git a/libpurple/plugins/ssl/ssl-gnutls.c b/libpurple/plugins/ssl/ssl-gnutls.c
index 0d6655c75a..3b9b08d3ff 100644
--- a/libpurple/plugins/ssl/ssl-gnutls.c
+++ b/libpurple/plugins/ssl/ssl-gnutls.c
@@ -698,9 +698,8 @@ x509_certificate_signed_by(PurpleCertificate * crt,
crt_issuer_id =
purple_certificate_get_issuer_unique_id(crt);
purple_debug_info("gnutls/x509",
- "Certificate for %s claims to be "
- "issued by %s, but the certificate "
- "for %s does not match.\n",
+ "Certificate %s is issued by "
+ "%s, which does not match %s.\n",
crt_id ? crt_id : "(null)",
crt_issuer_id ? crt_issuer_id : "(null)",
issuer_id ? issuer_id : "(null)");
@@ -730,6 +729,7 @@ x509_certificate_signed_by(PurpleCertificate * crt,
return FALSE;
}
+#ifdef HAVE_GNUTLS_CERT_INSECURE_ALGORITHM
if (verify & GNUTLS_CERT_INSECURE_ALGORITHM) {
/*
* A certificate in the chain is signed with an insecure
@@ -743,6 +743,7 @@ x509_certificate_signed_by(PurpleCertificate * crt,
"Insecure hash algorithm used by %s to sign %s\n",
issuer_id, crt_id);
}
+#endif
if (verify & GNUTLS_CERT_INVALID) {
/* Signature didn't check out, but at least
diff --git a/libpurple/protocols/Makefile.am b/libpurple/protocols/Makefile.am
index 97d1de9357..19a86c8974 100644
--- a/libpurple/protocols/Makefile.am
+++ b/libpurple/protocols/Makefile.am
@@ -1,5 +1,5 @@
EXTRA_DIST = Makefile.mingw
-DIST_SUBDIRS = bonjour gg irc jabber msn msnp9 myspace novell null oscar qq sametime silc silc10 simple yahoo zephyr
+DIST_SUBDIRS = bonjour gg irc jabber msn msnp9 myspace mxit novell null oscar qq sametime silc silc10 simple yahoo zephyr
SUBDIRS = $(DYNAMIC_PRPLS) $(STATIC_PRPLS)
diff --git a/libpurple/protocols/bonjour/mdns_avahi.c b/libpurple/protocols/bonjour/mdns_avahi.c
index 067dbed96e..6297f156b7 100644
--- a/libpurple/protocols/bonjour/mdns_avahi.c
+++ b/libpurple/protocols/bonjour/mdns_avahi.c
@@ -150,6 +150,10 @@ _resolver_callback(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiProtoco
}
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 @@ _resolver_callback(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiProtoco
/* Get the ip as a string */
+ ip[0] = '\0';
avahi_address_snprint(ip, AVAHI_ADDRESS_STR_MAX, a);
+ purple_debug_info("bonjour", "_resolve_callback - name:%s ip:%s prev_ip:%s\n",
+ name, ip, rd->ip);
+
if (rd->ip == NULL || strcmp(rd->ip, ip) != 0) {
/* We store duplicates in bb->ips, so we always remove the one */
if (rd->ip != NULL) {
diff --git a/libpurple/protocols/jabber/JEPS b/libpurple/protocols/jabber/JEPS
deleted file mode 100644
index cd67248431..0000000000
--- a/libpurple/protocols/jabber/JEPS
+++ /dev/null
@@ -1,27 +0,0 @@
-0045: IN PROGRESS
- Multi-User Chat
-0047: IN PROGRESS
- In-Band Bytestreams
-0060: NEED
- Pub-Sub
-0071: AWAITING FINAL SPEC
- XHTML-IM
-0073: NEED
- Basic IM Protocol Suite
-0080: NEED (Do we?)
- Geographic Location Information
-0084: NEED
- User Avatars in Jabber
-0085: NEED
- Chat State Notifications
-0089: WATCH
- Generic Alerts
-0093: NEED
- Roster Item Exchange
-0100: NEED
- Gateway Interaction (Transports)
-0115: WATCH
- Client Capabilities
-0117: NEED
- Intermediate IM Protocol Suite
-
diff --git a/libpurple/protocols/jabber/XEPS b/libpurple/protocols/jabber/XEPS
new file mode 100644
index 0000000000..5db60bfd4a
--- /dev/null
+++ b/libpurple/protocols/jabber/XEPS
@@ -0,0 +1,4 @@
+0060: NEED
+ Pub-Sub
+0080: NEED (Do we?)
+ Geographic Location Information
diff --git a/libpurple/protocols/jabber/auth.c b/libpurple/protocols/jabber/auth.c
index 60ca6592b8..8272af6666 100644
--- a/libpurple/protocols/jabber/auth.c
+++ b/libpurple/protocols/jabber/auth.c
@@ -58,7 +58,7 @@ jabber_process_starttls(JabberStream *js, xmlnode *packet)
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 @@ static void jabber_auth_start_cyrus(JabberStream *js)
* 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 @@ jabber_auth_start(JabberStream *js, xmlnode *packet)
* 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 @@ jabber_auth_start(JabberStream *js, xmlnode *packet)
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 @@ static void auth_old_result_cb(JabberStream *js, const char *from,
/* 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 @@ void jabber_auth_start_old(JabberStream *js)
* 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 @@ jabber_auth_handle_challenge(JabberStream *js, xmlnode *packet)
}
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 @@ jabber_auth_handle_challenge(JabberStream *js, xmlnode *packet)
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 @@ jabber_auth_handle_challenge(JabberStream *js, xmlnode *packet)
* 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 @@ void jabber_auth_handle_success(JabberStream *js, xmlnode *packet)
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 @@ void jabber_auth_handle_success(JabberStream *js, xmlnode *packet)
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 @@ void jabber_auth_handle_failure(JabberStream *js, xmlnode *packet)
#ifdef HAVE_CYRUS_SASL
if(js->auth_fail_count++ < 5) {
- if (js->current_mech && strlen(js->current_mech) > 0) {
+ if (js->current_mech && *js->current_mech) {
char *pos;
if ((pos = strstr(js->sasl_mechs->str, js->current_mech))) {
g_string_erase(js->sasl_mechs, pos-js->sasl_mechs->str, strlen(js->current_mech));
}
/* Remove space which separated this mech from the next */
- if (strlen(js->sasl_mechs->str) > 0 && ((js->sasl_mechs->str)[0] == ' ')) {
+ if ((js->sasl_mechs->str)[0] == ' ') {
g_string_erase(js->sasl_mechs, 0, 1);
}
}
- if (strlen(js->sasl_mechs->str)) {
+ if (*js->sasl_mechs->str) {
/* If we have remaining mechs to try, do so */
sasl_dispose(&js->sasl);
diff --git a/libpurple/protocols/jabber/bosh.c b/libpurple/protocols/jabber/bosh.c
index f7438ad99c..4868064915 100644
--- a/libpurple/protocols/jabber/bosh.c
+++ b/libpurple/protocols/jabber/bosh.c
@@ -401,7 +401,8 @@ jabber_bosh_connection_send(PurpleBOSHConnection *conn,
void jabber_bosh_connection_close(PurpleBOSHConnection *conn)
{
- jabber_bosh_connection_send(conn, PACKET_TERMINATE, NULL);
+ if (conn->state == BOSH_CONN_ONLINE)
+ jabber_bosh_connection_send(conn, PACKET_TERMINATE, NULL);
}
static gboolean jabber_bosh_connection_error_check(PurpleBOSHConnection *conn, xmlnode *node) {
diff --git a/libpurple/protocols/jabber/buddy.c b/libpurple/protocols/jabber/buddy.c
index f2a8853eb0..92683231fd 100644
--- a/libpurple/protocols/jabber/buddy.c
+++ b/libpurple/protocols/jabber/buddy.c
@@ -38,6 +38,7 @@
#include "xdata.h"
#include "pep.h"
#include "adhoccommands.h"
+#include "google.h"
typedef struct {
long idle_seconds;
@@ -580,8 +581,7 @@ jabber_format_info(PurpleConnection *gc, PurpleRequestFields *fields)
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) {
@@ -1149,9 +1149,8 @@ static void jabber_vcard_parse(JabberStream *js, const char *from,
char *bintext = NULL;
xmlnode *binval;
- if( ((binval = xmlnode_get_child(child, "BINVAL")) &&
- (bintext = xmlnode_get_data(binval))) ||
- (bintext = xmlnode_get_data(child))) {
+ if ((binval = xmlnode_get_child(child, "BINVAL")) &&
+ (bintext = xmlnode_get_data(binval))) {
gsize size;
guchar *data;
gboolean photo = (strcmp(child->name, "PHOTO") == 0);
@@ -1843,6 +1842,13 @@ static GList *jabber_buddy_menu(PurpleBuddy *buddy)
m = g_list_append(m, act);
}
+ if (js->googletalk) {
+ act = purple_menu_action_new(_("Initiate _Chat"),
+ PURPLE_CALLBACK(google_buddy_node_chat),
+ NULL, NULL);
+ m = g_list_append(m, act);
+ }
+
/*
* This if-condition implements parts of XEP-0100: Gateway Interaction
*
diff --git a/libpurple/protocols/jabber/caps.c b/libpurple/protocols/jabber/caps.c
index c05a1436b1..96edefdd97 100644
--- a/libpurple/protocols/jabber/caps.c
+++ b/libpurple/protocols/jabber/caps.c
@@ -975,7 +975,7 @@ void jabber_caps_calculate_own_hash(JabberStream *js) {
g_free(js->caps_hash);
js->caps_hash = jabber_caps_calculate_hash(&info, "sha1");
g_list_free(info.identities);
- g_list_free(features);
+ g_list_free(info.features);
}
const gchar* jabber_caps_get_own_hash(JabberStream *js)
diff --git a/libpurple/protocols/jabber/chat.c b/libpurple/protocols/jabber/chat.c
index 76ae78ca26..1c145bcb6a 100644
--- a/libpurple/protocols/jabber/chat.c
+++ b/libpurple/protocols/jabber/chat.c
@@ -106,7 +106,7 @@ JabberChat *jabber_chat_find(JabberStream *js, const char *room,
{
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 @@ void jabber_chat_invite(PurpleConnection *gc, int id, const char *msg,
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");
}
@@ -209,19 +220,98 @@ static void insert_in_hash_table(gpointer key, gpointer value, gpointer user_dat
g_hash_table_insert(hash_table, g_strdup(key), g_strdup(value));
}
-void jabber_chat_join(PurpleConnection *gc, GHashTable *data)
+static JabberChat *jabber_chat_new(JabberStream *js, const char *room,
+ const char *server, const char *handle,
+ const char *password, GHashTable *data)
{
JabberChat *chat;
- char *room, *server, *handle, *passwd;
- xmlnode *presence, *x;
- char *tmp, *room_jid, *full_jid;
- JabberStream *js = gc->proto_data;
- PurplePresence *gpresence;
+ char *jid;
+
+ if (jabber_chat_find(js, room, server) != NULL)
+ return NULL;
+
+ chat = g_new0(JabberChat, 1);
+ chat->js = js;
+
+ chat->room = g_strdup(room);
+ chat->server = g_strdup(server);
+ chat->handle = g_strdup(handle);
+
+ /* Copy the data hash table to chat->components */
+ chat->components = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, g_free);
+ if (data == NULL) {
+ g_hash_table_insert(chat->components, g_strdup("handle"), g_strdup(handle));
+ g_hash_table_insert(chat->components, g_strdup("room"), g_strdup(room));
+ g_hash_table_insert(chat->components, g_strdup("server"), g_strdup(server));
+ /* g_hash_table_insert(chat->components, g_strdup("password"), g_strdup(server)); */
+ } else {
+ g_hash_table_foreach(data, insert_in_hash_table, chat->components);
+ }
+
+ chat->members = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
+ (GDestroyNotify)jabber_chat_member_free);
+
+ jid = g_strdup_printf("%s@%s", room, server);
+ g_hash_table_insert(js->chats, jid, chat);
+
+ return chat;
+}
+
+JabberChat *jabber_join_chat(JabberStream *js, const char *room,
+ const char *server, const char *handle,
+ const char *password, GHashTable *data)
+{
+ JabberChat *chat;
+
+ PurpleConnection *gc;
+ PurpleAccount *account;
PurpleStatus *status;
+
+ xmlnode *presence, *x;
JabberBuddyState state;
char *msg;
int priority;
+ char *jid;
+
+ chat = jabber_chat_new(js, room, server, handle, password, data);
+ if (chat == NULL)
+ return NULL;
+
+ gc = js->gc;
+ account = purple_connection_get_account(gc);
+ status = purple_account_get_active_status(account);
+ purple_status_to_jabber(status, &state, &msg, &priority);
+
+ presence = jabber_presence_create_js(js, state, msg, priority);
+ g_free(msg);
+
+ jid = g_strdup_printf("%s@%s/%s", room, server, handle);
+ xmlnode_set_attrib(presence, "to", jid);
+ g_free(jid);
+
+ x = xmlnode_new_child(presence, "x");
+ xmlnode_set_namespace(x, "http://jabber.org/protocol/muc");
+
+ if (password && *password) {
+ xmlnode *p = xmlnode_new_child(x, "password");
+ xmlnode_insert_data(p, password, -1);
+ }
+
+ jabber_send(js, presence);
+ xmlnode_free(presence);
+
+ return chat;
+}
+
+void jabber_chat_join(PurpleConnection *gc, GHashTable *data)
+{
+ char *room, *server, *handle, *passwd;
+ JabberID *jid;
+ JabberStream *js = gc->proto_data;
+ char *tmp;
+
room = g_hash_table_lookup(data, "room");
server = g_hash_table_lookup(data, "server");
handle = g_hash_table_lookup(data, "handle");
@@ -256,51 +346,23 @@ void jabber_chat_join(PurpleConnection *gc, GHashTable *data)
return;
}
- if(jabber_chat_find(js, room, server))
- return;
-
+ /* Normalize the room and server parameters */
tmp = g_strdup_printf("%s@%s", room, server);
- room_jid = g_strdup(jabber_normalize(NULL, tmp));
+ jid = jabber_id_new(tmp);
g_free(tmp);
- chat = g_new0(JabberChat, 1);
- chat->js = gc->proto_data;
-
- chat->room = g_strdup(room);
- chat->server = g_strdup(server);
- chat->handle = g_strdup(handle);
-
- /* Copy the data hash table to chat->components */
- chat->components = g_hash_table_new_full(g_str_hash, g_str_equal,
- g_free, g_free);
- g_hash_table_foreach(data, insert_in_hash_table, chat->components);
-
- chat->members = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
- (GDestroyNotify)jabber_chat_member_free);
-
- g_hash_table_insert(js->chats, room_jid, chat);
+ if (jid == NULL) {
+ /* TODO: Error message */
- gpresence = purple_account_get_presence(gc->account);
- status = purple_presence_get_active_status(gpresence);
-
- purple_status_to_jabber(status, &state, &msg, &priority);
-
- presence = jabber_presence_create_js(js, state, msg, priority);
- full_jid = g_strdup_printf("%s/%s", room_jid, handle);
- xmlnode_set_attrib(presence, "to", full_jid);
- g_free(full_jid);
- g_free(msg);
-
- x = xmlnode_new_child(presence, "x");
- xmlnode_set_namespace(x, "http://jabber.org/protocol/muc");
-
- if(passwd && *passwd) {
- xmlnode *password = xmlnode_new_child(x, "password");
- xmlnode_insert_data(password, passwd, -1);
+ g_return_if_reached();
}
- jabber_send(js, presence);
- xmlnode_free(presence);
+ /*
+ * Now that we've done all that nice core-interface stuff, let's join
+ * this room!
+ */
+ jabber_join_chat(js, jid->node, jid->domain, handle, passwd, data);
+ jabber_id_free(jid);
}
void jabber_chat_leave(PurpleConnection *gc, int id)
@@ -322,7 +384,7 @@ void jabber_chat_destroy(JabberChat *chat)
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);
}
@@ -630,11 +692,11 @@ 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)
{
xmlnode *presence;
char *full_jid;
- PurplePresence *gpresence;
+ PurpleAccount *account;
PurpleStatus *status;
JabberBuddyState state;
char *msg;
@@ -644,11 +706,11 @@ void jabber_chat_change_nick(JabberChat *chat, const char *nick)
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);
@@ -660,6 +722,8 @@ void jabber_chat_change_nick(JabberChat *chat, const char *nick)
jabber_send(chat->js, presence);
xmlnode_free(presence);
+
+ return TRUE;
}
void jabber_chat_part(JabberChat *chat, const char *msg)
diff --git a/libpurple/protocols/jabber/chat.h b/libpurple/protocols/jabber/chat.h
index 466bafb0c5..2567fec861 100644
--- a/libpurple/protocols/jabber/chat.h
+++ b/libpurple/protocols/jabber/chat.h
@@ -57,6 +57,21 @@ typedef struct _JabberChat {
GList *jabber_chat_info(PurpleConnection *gc);
GHashTable *jabber_chat_info_defaults(PurpleConnection *gc, const char *chat_name);
char *jabber_get_chat_name(GHashTable *data);
+
+/**
+ * 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.)
+ */
+JabberChat *jabber_join_chat(JabberStream *js, const char *room,
+ const char *server, const char *handle,
+ const char *password, GHashTable *data);
+
void jabber_chat_join(PurpleConnection *gc, GHashTable *data);
JabberChat *jabber_chat_find(JabberStream *js, const char *room,
const char *server);
@@ -74,7 +89,7 @@ void jabber_chat_create_instant_room(JabberChat *chat);
void jabber_chat_register(JabberChat *chat);
void jabber_chat_change_topic(JabberChat *chat, const char *topic);
void jabber_chat_set_topic(PurpleConnection *gc, int id, const char *topic);
-void jabber_chat_change_nick(JabberChat *chat, const char *nick);
+gboolean jabber_chat_change_nick(JabberChat *chat, const char *nick);
void jabber_chat_part(JabberChat *chat, const char *msg);
void jabber_chat_track_handle(JabberChat *chat, const char *handle,
const char *jid, const char *affiliation, const char *role);
diff --git a/libpurple/protocols/jabber/disco.c b/libpurple/protocols/jabber/disco.c
index 42c3425563..de0dccb2dd 100644
--- a/libpurple/protocols/jabber/disco.c
+++ b/libpurple/protocols/jabber/disco.c
@@ -421,6 +421,76 @@ jabber_disco_finish_server_info_result_cb(JabberStream *js)
}
+/* 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 @@ jabber_disco_server_info_result_cb(JabberStream *js, const char *from,
/* autodiscover stun and relays */
jabber_google_send_jingle_info(js);
} else {
- /* TODO: add external service discovery here... */
+ js->srv_query_data =
+ purple_srv_resolve("stun", "udp", js->user->domain,
+ jabber_disco_stun_srv_resolve_cb, js);
+ /* TODO: add TURN support later... */
}
}
diff --git a/libpurple/protocols/jabber/google.c b/libpurple/protocols/jabber/google.c
index f778a52048..0d5bf86c3f 100644
--- a/libpurple/protocols/jabber/google.c
+++ b/libpurple/protocols/jabber/google.c
@@ -31,6 +31,7 @@
#include "jabber.h"
#include "presence.h"
#include "iq.h"
+#include "chat.h"
#include "jingle/jingle.h"
@@ -97,14 +98,14 @@ google_session_send_candidates(PurpleMedia *media, gchar *session_id,
gchar *participant, GoogleSession *session)
{
GList *candidates = purple_media_get_local_candidates(
- session->media, session_id, session->remote_jid);
+ session->media, session_id, session->remote_jid), *iter;
PurpleMediaCandidate *transport;
gboolean video = FALSE;
if (!strcmp(session_id, "google-video"))
video = TRUE;
- for (;candidates;candidates = candidates->next) {
+ for (iter = candidates; iter; iter = iter->next) {
JabberIq *iq;
gchar *ip, *port, *username, *password;
gchar pref[16];
@@ -112,7 +113,7 @@ google_session_send_candidates(PurpleMedia *media, gchar *session_id,
xmlnode *sess;
xmlnode *candidate;
guint component_id;
- transport = (PurpleMediaCandidate*)(candidates->data);
+ transport = PURPLE_MEDIA_CANDIDATE(iter->data);
component_id = purple_media_candidate_get_component_id(
transport);
@@ -170,6 +171,7 @@ google_session_send_candidates(PurpleMedia *media, gchar *session_id,
jabber_iq_send(iq);
}
+ purple_media_candidate_list_free(candidates);
}
static void
@@ -1075,7 +1077,7 @@ jabber_gmail_poke(JabberStream *js, const char *from, JabberIqType type,
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");
@@ -1159,8 +1161,9 @@ gboolean jabber_google_roster_incoming(JabberStream *js, xmlnode *item)
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.
*/
@@ -1258,12 +1261,13 @@ void jabber_google_roster_add_deny(PurpleConnection *gc, const char *who)
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);
}
@@ -1607,3 +1611,43 @@ jabber_google_send_jingle_info(JabberStream *js)
purple_debug_info("jabber", "sending google:jingleinfo query\n");
jabber_iq_send(jingle_info);
}
+
+void google_buddy_node_chat(PurpleBlistNode *node, gpointer data)
+{
+ PurpleBuddy *buddy;
+ PurpleConnection *gc;
+ JabberStream *js;
+ JabberChat *chat;
+ gchar *room;
+ guint32 tmp, a, b;
+
+ g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
+
+ buddy = PURPLE_BUDDY(node);
+ gc = purple_account_get_connection(purple_buddy_get_account(buddy));
+ g_return_if_fail(gc != NULL);
+ js = purple_connection_get_protocol_data(gc);
+
+ /* Generate a version 4 UUID */
+ tmp = g_random_int();
+ a = 0x4000 | (tmp & 0xFFF); /* 0x4000 to 0x4FFF */
+ tmp >>= 12;
+ b = ((1 << 3) << 12) | (tmp & 0x3FFF); /* 0x8000 to 0xBFFF */
+
+ tmp = g_random_int();
+ room = g_strdup_printf("private-chat-%08x-%04x-%04x-%04x-%04x%08x",
+ g_random_int(),
+ tmp & 0xFFFF,
+ a,
+ b,
+ (tmp >> 16) & 0xFFFF, g_random_int());
+
+ chat = jabber_join_chat(js, room, GOOGLE_GROUPCHAT_SERVER, js->user->node,
+ NULL, NULL);
+ if (chat) {
+ chat->muc = TRUE;
+ jabber_chat_invite(gc, chat->id, "", buddy->name);
+ }
+
+ g_free(room);
+}
diff --git a/libpurple/protocols/jabber/google.h b/libpurple/protocols/jabber/google.h
index ae6d21cef5..8489a63028 100644
--- a/libpurple/protocols/jabber/google.h
+++ b/libpurple/protocols/jabber/google.h
@@ -31,6 +31,8 @@
#define GOOGLE_VIDEO_CAP "http://www.google.com/xmpp/protocol/video/v1"
#define GOOGLE_JINGLE_INFO_NAMESPACE "google:jingleinfo"
+#define GOOGLE_GROUPCHAT_SERVER "groupchat.google.com"
+
void jabber_gmail_init(JabberStream *js);
void jabber_gmail_poke(JabberStream *js, const char *from, JabberIqType type,
const char *id, xmlnode *new_mail);
@@ -59,4 +61,6 @@ void jabber_google_handle_jingle_info(JabberStream *js, const char *from,
xmlnode *child);
void jabber_google_send_jingle_info(JabberStream *js);
+void google_buddy_node_chat(PurpleBlistNode *node, gpointer data);
+
#endif /* PURPLE_JABBER_GOOGLE_H_ */
diff --git a/libpurple/protocols/jabber/iq.c b/libpurple/protocols/jabber/iq.c
index 299aa3ce8a..08a52fb181 100644
--- a/libpurple/protocols/jabber/iq.c
+++ b/libpurple/protocols/jabber/iq.c
@@ -342,7 +342,7 @@ void jabber_iq_parse(JabberStream *js, xmlnode *packet)
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 @@ void jabber_iq_parse(JabberStream *js, xmlnode *packet)
g_free(key);
if (signal_ref > 0) {
- signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(jabber_plugin, "jabber-watched-iq",
+ signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(purple_connection_get_prpl(js->gc), "jabber-watched-iq",
js->gc, iq_type, id, from, child));
if (signal_return)
return;
diff --git a/libpurple/protocols/jabber/jabber.c b/libpurple/protocols/jabber/jabber.c
index 115fb9c6e0..b6130e1992 100644
--- a/libpurple/protocols/jabber/jabber.c
+++ b/libpurple/protocols/jabber/jabber.c
@@ -68,12 +68,9 @@
#include "jingle/jingle.h"
#include "jingle/rtp.h"
-#define JABBER_CONNECT_STEPS (js->gsc ? 9 : 5)
-
-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);
@@ -198,10 +195,11 @@ static char *jabber_prep_resource(char *input) {
void jabber_stream_features_parse(JabberStream *js, xmlnode *packet)
{
if(xmlnode_get_child(packet, "starttls")) {
- if(jabber_process_starttls(js, packet))
-
+ if(jabber_process_starttls(js, packet)) {
+ 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."));
@@ -211,6 +209,7 @@ void jabber_stream_features_parse(JabberStream *js, xmlnode *packet)
if(js->registration) {
jabber_register_start(js);
} else if(xmlnode_get_child(packet, "mechanisms")) {
+ jabber_stream_set_state(js, JABBER_STREAM_AUTHENTICATING);
jabber_auth_start(js, packet);
} else if(xmlnode_get_child(packet, "bind")) {
xmlnode *bind, *resource;
@@ -255,7 +254,7 @@ void jabber_process_packet(JabberStream *js, xmlnode **packet)
{
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)
@@ -289,11 +288,12 @@ void jabber_process_packet(JabberStream *js, xmlnode **packet)
if(js->state == JABBER_STREAM_AUTHENTICATING)
jabber_auth_handle_failure(js, *packet);
} else if(!strcmp((*packet)->name, "proceed")) {
- if(js->state == JABBER_STREAM_AUTHENTICATING && !js->gsc)
+ if (js->state == JABBER_STREAM_INITIALIZING_ENCRYPTION && !js->gsc)
tls_init(js);
+ 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);
}
}
@@ -375,9 +375,9 @@ static gboolean do_jabber_send_raw(JabberStream *js, const char *data, int len)
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 */
@@ -402,8 +402,13 @@ void jabber_send_raw(JabberStream *js, const char *data, int len)
*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 : "");
@@ -413,7 +418,7 @@ void jabber_send_raw(JabberStream *js, const char *data, int len)
/* 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;
@@ -483,7 +488,7 @@ void jabber_send_signal_cb(PurpleConnection *pc, xmlnode **packet,
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)
@@ -524,7 +529,7 @@ jabber_recv_cb_ssl(gpointer data, PurpleSslConnection *gsc,
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);
@@ -564,7 +569,7 @@ jabber_recv_cb(gpointer data, gint source, PurpleInputCondition condition)
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);
@@ -573,7 +578,7 @@ jabber_recv_cb(gpointer data, gint source, PurpleInputCondition condition)
}
#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);
@@ -1080,53 +1085,33 @@ jabber_register_cb(JabberRegisterCBData *cbdata, PurpleRequestFields *fields)
return;
}
} else {
- const char *value = purple_request_field_string_get_value(field);
-
- if(!strcmp(id, "username")) {
- y = xmlnode_new_child(query, "username");
- } else if(!strcmp(id, "password")) {
- y = xmlnode_new_child(query, "password");
- } else if(!strcmp(id, "name")) {
- y = xmlnode_new_child(query, "name");
- } else if(!strcmp(id, "email")) {
- y = xmlnode_new_child(query, "email");
- } else if(!strcmp(id, "nick")) {
- y = xmlnode_new_child(query, "nick");
- } else if(!strcmp(id, "first")) {
- y = xmlnode_new_child(query, "first");
- } else if(!strcmp(id, "last")) {
- y = xmlnode_new_child(query, "last");
- } else if(!strcmp(id, "address")) {
- y = xmlnode_new_child(query, "address");
- } else if(!strcmp(id, "city")) {
- y = xmlnode_new_child(query, "city");
- } else if(!strcmp(id, "state")) {
- y = xmlnode_new_child(query, "state");
- } else if(!strcmp(id, "zip")) {
- y = xmlnode_new_child(query, "zip");
- } else if(!strcmp(id, "phone")) {
- y = xmlnode_new_child(query, "phone");
- } else if(!strcmp(id, "url")) {
- y = xmlnode_new_child(query, "url");
- } else if(!strcmp(id, "date")) {
- y = xmlnode_new_child(query, "date");
- } else {
- continue;
- }
- xmlnode_insert_data(y, value, -1);
+ const char *ids[] = {"username", "password", "name", "email", "nick", "first",
+ "last", "address", "city", "state", "zip", "phone", "url", "date",
+ NULL};
+ const char *value = purple_request_field_string_get_value(field);
+ int i;
+ for (i = 0; ids[i]; i++) {
+ if (!strcmp(id, ids[i]))
+ break;
+ }
+
+ if (!ids[i])
+ continue;
+ y = xmlnode_new_child(query, ids[i]);
+ xmlnode_insert_data(y, value, -1);
if(cbdata->js->registration && !strcmp(id, "username")) {
g_free(cbdata->js->user->node);
cbdata->js->user->node = g_strdup(value);
- }
+ }
if(cbdata->js->registration && !strcmp(id, "password"))
purple_account_set_password(cbdata->js->gc->account, value);
+ }
}
}
- }
if(cbdata->js->registration) {
- username = g_strdup_printf("%s@%s/%s", cbdata->js->user->node, cbdata->js->user->domain,
- cbdata->js->user->resource);
+ username = g_strdup_printf("%s@%s%s%s", cbdata->js->user->node, cbdata->js->user->domain,
+ cbdata->js->user->resource ? "/" : "", cbdata->js->user->resource);
purple_account_set_username(cbdata->js->gc->account, username);
g_free(username);
}
@@ -1588,6 +1573,8 @@ void jabber_close(PurpleConnection *gc)
void jabber_stream_set_state(JabberStream *js, JabberStreamState state)
{
+#define JABBER_CONNECT_STEPS ((js->gsc || js->state == JABBER_STREAM_INITIALIZING_ENCRYPTION) ? 9 : 5)
+
js->state = state;
switch(state) {
case JABBER_STREAM_OFFLINE:
@@ -1625,6 +1612,8 @@ void jabber_stream_set_state(JabberStream *js, JabberStreamState state)
purple_connection_set_state(js->gc, PURPLE_CONNECTED);
break;
}
+
+#undef JABBER_CONNECT_STEPS
}
char *jabber_get_next_id(JabberStream *js)
@@ -1857,7 +1846,7 @@ static void jabber_features_destroy(void)
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);
}
}
@@ -1894,7 +1883,7 @@ static void jabber_identities_destroy(void)
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);
}
}
@@ -2638,8 +2627,15 @@ static PurpleCmdRet jabber_cmd_chat_nick(PurpleConversation *conv,
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,
@@ -3274,7 +3270,7 @@ void jabber_register_commands(void)
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));
@@ -3429,8 +3425,6 @@ jabber_init_plugin(PurplePlugin *plugin)
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 ||
@@ -3520,9 +3514,9 @@ jabber_init_plugin(PurplePlugin *plugin)
}
void
-jabber_uninit_plugin(void)
+jabber_uninit_plugin(PurplePlugin *plugin)
{
- purple_plugin_ipc_unregister_all(jabber_plugin);
+ purple_plugin_ipc_unregister_all(plugin);
jabber_features_destroy();
jabber_identities_destroy();
diff --git a/libpurple/protocols/jabber/jabber.h b/libpurple/protocols/jabber/jabber.h
index 7b02d8a7b3..a7ffde05c5 100644
--- a/libpurple/protocols/jabber/jabber.h
+++ b/libpurple/protocols/jabber/jabber.h
@@ -76,11 +76,11 @@ typedef struct _JabberStream JabberStream;
#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 @@ struct _JabberStream
char *serverFQDN;
- /* OK, this stays at the end of the struct, so plugins can depend
- * on the rest of the stuff being in the right place
- */
#ifdef HAVE_CYRUS_SASL
sasl_conn_t *sasl;
sasl_callback_t *sasl_cb;
-#else /* keep the struct the same size */
- void *sasl;
- void *sasl_cb;
-#endif
- /* did someone say something about the end of the struct? */
-#ifdef HAVE_CYRUS_SASL
const char *current_mech;
int auth_fail_count;
-#endif
int sasl_state;
int sasl_maxbuf;
GString *sasl_mechs;
+#endif
gboolean unregistration;
PurpleAccountUnregistrationCb unregistration_cb;
@@ -387,6 +378,6 @@ void jabber_register_commands(void);
void jabber_unregister_commands(void);
void jabber_init_plugin(PurplePlugin *plugin);
-void jabber_uninit_plugin(void);
+void jabber_uninit_plugin(PurplePlugin *plugin);
#endif /* PURPLE_JABBER_H_ */
diff --git a/libpurple/protocols/jabber/jingle/content.c b/libpurple/protocols/jabber/jingle/content.c
index f57fce6255..c049410d06 100644
--- a/libpurple/protocols/jabber/jingle/content.c
+++ b/libpurple/protocols/jabber/jingle/content.c
@@ -330,8 +330,8 @@ jingle_content_get_transport(JingleContent *content)
void
jingle_content_set_session(JingleContent *content, JingleSession *session)
{
- JINGLE_IS_CONTENT(content);
- JINGLE_IS_SESSION(session);
+ g_return_if_fail(JINGLE_IS_CONTENT(content));
+ g_return_if_fail(JINGLE_IS_SESSION(session));
g_object_set(content, "session", session, NULL);
}
diff --git a/libpurple/protocols/jabber/libxmpp.c b/libpurple/protocols/jabber/libxmpp.c
index d2a587ecbd..bf945363a4 100644
--- a/libpurple/protocols/jabber/libxmpp.c
+++ b/libpurple/protocols/jabber/libxmpp.c
@@ -227,7 +227,7 @@ static gboolean unload_plugin(PurplePlugin *plugin)
jabber_unregister_commands();
/* Stay on target...stay on target... Almost there... */
- jabber_uninit_plugin();
+ jabber_uninit_plugin(plugin);
return TRUE;
}
@@ -355,7 +355,7 @@ init_plugin(PurplePlugin *plugin)
purple_account_user_split_set_reverse(split, FALSE);
prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
- option = purple_account_option_bool_new(_("Require SSL/TLS"), "require_tls", TRUE);
+ option = purple_account_option_bool_new(_("Require SSL/TLS"), "require_tls", JABBER_DEFAULT_REQUIRE_TLS);
prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
option);
diff --git a/libpurple/protocols/jabber/message.c b/libpurple/protocols/jabber/message.c
index 3104b709f5..c553ac6185 100644
--- a/libpurple/protocols/jabber/message.c
+++ b/libpurple/protocols/jabber/message.c
@@ -545,7 +545,7 @@ void jabber_message_parse(JabberStream *js, xmlnode *packet)
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 @@ void jabber_message_parse(JabberStream *js, xmlnode *packet)
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 @@ void jabber_message_parse(JabberStream *js, xmlnode *packet)
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 @@ void jabber_message_parse(JabberStream *js, xmlnode *packet)
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 @@ void jabber_message_send(JabberMessage *jm)
if ((child = xmlnode_from_str(jm->xhtml, -1))) {
xmlnode_insert_child(message, child);
} else {
- purple_debug(PURPLE_DEBUG_ERROR, "jabber",
+ purple_debug_error("jabber",
"XHTML translation/validation failed, returning: %s\n",
jm->xhtml);
}
diff --git a/libpurple/protocols/jabber/oob.c b/libpurple/protocols/jabber/oob.c
index 468e2706f9..de2a0a92aa 100644
--- a/libpurple/protocols/jabber/oob.c
+++ b/libpurple/protocols/jabber/oob.c
@@ -95,7 +95,7 @@ static void jabber_oob_xfer_request_send(gpointer data, gint source, PurpleInput
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 @@ static gssize jabber_oob_xfer_read(guchar **buffer, PurpleXfer *xfer) {
}
return 0;
} else if (errno != EAGAIN) {
- purple_debug(PURPLE_DEBUG_ERROR, "jabber", "Read error on oob xfer!\n");
+ purple_debug_error("jabber", "Read error on oob xfer!\n");
purple_xfer_cancel_local(xfer);
}
diff --git a/libpurple/protocols/jabber/parser.c b/libpurple/protocols/jabber/parser.c
index e2115c6412..9ab7a24176 100644
--- a/libpurple/protocols/jabber/parser.c
+++ b/libpurple/protocols/jabber/parser.c
@@ -62,11 +62,6 @@ jabber_parser_element_start_libxml(void *user_data,
g_free(attrib);
}
}
- if(js->protocol_version == JABBER_PROTO_0_9)
- js->auth_type = JABBER_AUTH_IQ_AUTH;
-
- if(js->state == JABBER_STREAM_INITIALIZING || js->state == JABBER_STREAM_INITIALIZING_ENCRYPTION)
- jabber_stream_set_state(js, JABBER_STREAM_AUTHENTICATING);
} else {
if(js->current)
@@ -256,5 +251,17 @@ void jabber_parser_process(JabberStream *js, const char *buf, int len)
break;
}
}
+
+ if (js->protocol_version == JABBER_PROTO_0_9 && !js->gc->disconnect_timeout &&
+ (js->state == JABBER_STREAM_INITIALIZING ||
+ js->state == JABBER_STREAM_INITIALIZING_ENCRYPTION)) {
+ /*
+ * Legacy servers don't advertise features, so if we've just gotten
+ * the opening <stream:stream> and there was no version, we need to
+ * immediately start legacy IQ auth.
+ */
+ js->auth_type = JABBER_AUTH_IQ_AUTH;
+ jabber_stream_set_state(js, JABBER_STREAM_AUTHENTICATING);
+ }
}
diff --git a/libpurple/protocols/jabber/presence.c b/libpurple/protocols/jabber/presence.c
index bb88c1189e..9f4bc46cfd 100644
--- a/libpurple/protocols/jabber/presence.c
+++ b/libpurple/protocols/jabber/presence.c
@@ -403,19 +403,20 @@ jabber_vcard_parse_avatar(JabberStream *js, const char *from,
g_free(nickname);
}
- if((photo = xmlnode_get_child(vcard, "PHOTO")) &&
- (( (binval = xmlnode_get_child(photo, "BINVAL")) &&
- (text = xmlnode_get_data(binval))) ||
- (text = xmlnode_get_data(photo)))) {
+ if ((photo = xmlnode_get_child(vcard, "PHOTO")) &&
+ (binval = xmlnode_get_child(photo, "BINVAL")) &&
+ (text = xmlnode_get_data(binval))) {
guchar *data;
- gchar *hash;
gsize size;
data = purple_base64_decode(text, &size);
- hash = jabber_calculate_data_sha1sum(data, size);
+ if (data) {
+ gchar *hash = jabber_calculate_data_sha1sum(data, size);
+ purple_buddy_icons_set_for_user(js->gc->account, from, data,
+ size, hash);
+ g_free(hash);
+ }
- purple_buddy_icons_set_for_user(js->gc->account, from, data, size, hash);
- g_free(hash);
g_free(text);
}
}
@@ -475,7 +476,7 @@ jabber_presence_set_capabilities(JabberCapsClientInfo *info, GList *exts,
/*
* 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;
@@ -517,7 +518,7 @@ void jabber_presence_parse(JabberStream *js, xmlnode *packet)
jb = jabber_buddy_find(js, from, TRUE);
g_return_if_fail(jb != NULL);
- signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(jabber_plugin,
+ signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(purple_connection_get_prpl(js->gc),
"jabber-receiving-presence", js->gc, type, from, packet));
if (signal_return)
return;
diff --git a/libpurple/protocols/jabber/roster.c b/libpurple/protocols/jabber/roster.c
index 9b1c315bc3..b2d95695be 100644
--- a/libpurple/protocols/jabber/roster.c
+++ b/libpurple/protocols/jabber/roster.c
@@ -72,7 +72,7 @@ static void add_purple_buddy_to_groups(JabberStream *js, const char *jid,
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 @@ static void add_purple_buddy_to_groups(JabberStream *js, const char *jid,
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 @@ static void add_purple_buddy_to_groups(JabberStream *js, const char *jid,
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 @@ static void add_purple_buddy_to_groups(JabberStream *js, const char *jid,
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);
}
@@ -229,7 +200,7 @@ void jabber_roster_parse(JabberStream *js, const char *from,
else
jb->subscription &= ~JABBER_SUB_PENDING;
- if(jb->subscription == JABBER_SUB_REMOVE) {
+ if(jb->subscription & JABBER_SUB_REMOVE) {
remove_purple_buddies(js, jid);
} else {
GSList *groups = NULL;
diff --git a/libpurple/protocols/jabber/si.c b/libpurple/protocols/jabber/si.c
index 4ba7d1f262..02f6762e30 100644
--- a/libpurple/protocols/jabber/si.c
+++ b/libpurple/protocols/jabber/si.c
@@ -1349,7 +1349,7 @@ static void jabber_si_xfer_cancel_send(PurpleXfer *xfer)
jabber_ibb_session_close(jsx->ibb_session);
}
jabber_si_xfer_free(xfer);
- purple_debug(PURPLE_DEBUG_INFO, "jabber", "in jabber_si_xfer_cancel_send\n");
+ purple_debug_info("jabber", "in jabber_si_xfer_cancel_send\n");
}
@@ -1381,7 +1381,7 @@ static void jabber_si_xfer_request_denied(PurpleXfer *xfer)
}
jabber_si_xfer_free(xfer);
- purple_debug(PURPLE_DEBUG_INFO, "jabber", "in jabber_si_xfer_request_denied\n");
+ purple_debug_info("jabber", "in jabber_si_xfer_request_denied\n");
}
@@ -1393,7 +1393,7 @@ static void jabber_si_xfer_cancel_recv(PurpleXfer *xfer)
jabber_ibb_session_close(jsx->ibb_session);
}
jabber_si_xfer_free(xfer);
- purple_debug(PURPLE_DEBUG_INFO, "jabber", "in jabber_si_xfer_cancel_recv\n");
+ purple_debug_info("jabber", "in jabber_si_xfer_cancel_recv\n");
}
diff --git a/libpurple/protocols/jabber/useravatar.c b/libpurple/protocols/jabber/useravatar.c
index 1f58264649..7851067dff 100644
--- a/libpurple/protocols/jabber/useravatar.c
+++ b/libpurple/protocols/jabber/useravatar.c
@@ -262,7 +262,7 @@ do_buddy_avatar_update_fromurl(PurpleUtilFetchUrlData *url_data,
gpointer icon_data;
if(!url_text) {
- purple_debug(PURPLE_DEBUG_ERROR, "jabber",
+ purple_debug_error("jabber",
"do_buddy_avatar_update_fromurl got error \"%s\"",
error_message);
goto out;
diff --git a/libpurple/protocols/msn/contact.c b/libpurple/protocols/msn/contact.c
index fc9e160832..3275e8313a 100644
--- a/libpurple/protocols/msn/contact.c
+++ b/libpurple/protocols/msn/contact.c
@@ -206,6 +206,7 @@ msn_contact_request_cb(MsnSoapMessage *req, MsnSoapMessage *resp,
"Operation {%s} failed. No response received from server.\n",
msn_contact_operation_str(state->action));
msn_session_set_error(state->session, MSN_ERROR_BAD_BLIST, NULL);
+ msn_callback_state_free(state);
return;
}
@@ -355,19 +356,26 @@ msn_parse_each_member(MsnSession *session, xmlnode *member, const char *node,
char *type;
char *member_id;
MsnUser *user;
- xmlnode *annotation;
+ xmlnode *annotation, *display;
guint nid = MSN_NETWORK_UNKNOWN;
char *invite = NULL;
+ 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;
}
type = xmlnode_get_data(xmlnode_get_child(member, "Type"));
member_id = xmlnode_get_data(xmlnode_get_child(member, "MembershipId"));
- user = msn_userlist_find_add_user(session->userlist, passport, NULL);
+ if ((display = xmlnode_get_child(member, "DisplayName"))) {
+ display_text = xmlnode_get_data(display);
+ } else {
+ display_text = NULL;
+ }
+
+ user = msn_userlist_find_add_user(session->userlist, passport, display_text);
for (annotation = xmlnode_get_child(member, "Annotations/Annotation");
annotation;
@@ -408,6 +416,7 @@ msn_parse_each_member(MsnSession *session, xmlnode *member, const char *node,
g_free(type);
g_free(member_id);
g_free(invite);
+ g_free(display_text);
}
static void
@@ -756,7 +765,7 @@ msn_parse_addressbook_contacts(MsnSession *session, xmlnode *node)
if (passport == NULL)
continue;
- if (!purple_email_is_valid(passport))
+ if (!msn_email_is_valid(passport))
continue;
if ((displayName = xmlnode_get_child(contactInfo, "displayName")))
@@ -1223,8 +1232,13 @@ msn_add_contact_to_group(MsnSession *session, MsnCallbackState *state,
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);
@@ -1417,12 +1431,20 @@ msn_update_contact(MsnSession *session, const char *passport, MsnContactUpdateTy
xmlnode *contact;
xmlnode *contact_info;
xmlnode *changes;
+ MsnUser *user = NULL;
- purple_debug_info("msn", "Update contact information with new %s: %s\n",
+ purple_debug_info("msn", "Update contact information for %s with new %s: %s\n",
+ passport ? passport : "(null)",
type == MSN_UPDATE_DISPLAY ? "display name" : "alias",
value ? value : "(null)");
- purple_debug_info("msn", "passport=%s\n", passport);
g_return_if_fail(passport != NULL);
+
+ if (strcmp(passport, "Me") != 0) {
+ user = msn_userlist_find_user(session->userlist, passport);
+ if (!user)
+ return;
+ }
+
contact_info = xmlnode_new("contactInfo");
changes = xmlnode_new("propertiesChanged");
@@ -1451,8 +1473,6 @@ msn_update_contact(MsnSession *session, const char *passport, MsnContactUpdateTy
g_return_if_reached();
}
-
-
state = msn_callback_state_new(session);
state->body = xmlnode_from_str(MSN_CONTACT_UPDATE_TEMPLATE, -1);
@@ -1465,14 +1485,13 @@ msn_update_contact(MsnSession *session, const char *passport, MsnContactUpdateTy
xmlnode_insert_child(contact, contact_info);
xmlnode_insert_child(contact, changes);
- if (!strcmp(passport, "Me")) {
- xmlnode *contactType = xmlnode_new_child(contact_info, "contactType");
- xmlnode_insert_data(contactType, "Me", -1);
- } else {
- MsnUser *user = msn_userlist_find_user(session->userlist, passport);
+ if (user) {
xmlnode *contactId = xmlnode_new_child(contact, "contactId");
msn_callback_state_set_uid(state, user->uid);
xmlnode_insert_data(contactId, state->uid, -1);
+ } else {
+ xmlnode *contactType = xmlnode_new_child(contact_info, "contactType");
+ xmlnode_insert_data(contactType, "Me", -1);
}
msn_contact_request(state);
diff --git a/libpurple/protocols/msn/msn.c b/libpurple/protocols/msn/msn.c
index 3663d46e56..7a8fd179de 100644
--- a/libpurple/protocols/msn/msn.c
+++ b/libpurple/protocols/msn/msn.c
@@ -118,6 +118,29 @@ msn_normalize(const PurpleAccount *account, const char *str)
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 @@ msn_can_receive_file(PurpleConnection *gc, const char *who)
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 @@ msn_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
bname = purple_buddy_get_name(buddy);
- if (!purple_email_is_valid(bname)) {
+ if (!msn_email_is_valid(bname)) {
gchar *buf;
buf = g_strdup_printf(_("Unable to add the buddy %s because the username is invalid. Usernames must be valid email addresses."), bname);
if (!purple_conv_present_error(bname, purple_connection_get_account(gc), buf))
diff --git a/libpurple/protocols/msn/msn.h b/libpurple/protocols/msn/msn.h
index b9369aee01..863d2e924b 100644
--- a/libpurple/protocols/msn/msn.h
+++ b/libpurple/protocols/msn/msn.h
@@ -133,6 +133,7 @@ typedef enum
((MSN_CLIENT_ID_VERSION << 24) | \
(MSN_CLIENT_ID_CAPABILITIES))
+gboolean msn_email_is_valid(const char *passport);
void msn_act_id(PurpleConnection *gc, const char *entry);
void msn_send_privacy(PurpleConnection *gc);
void msn_send_im_message(MsnSession *session, MsnMessage *msg);
diff --git a/libpurple/protocols/msn/nexus.c b/libpurple/protocols/msn/nexus.c
index 6707a7f627..f945346959 100644
--- a/libpurple/protocols/msn/nexus.c
+++ b/libpurple/protocols/msn/nexus.c
@@ -338,8 +338,10 @@ nexus_parse_collection(MsnNexus *nexus, int id, xmlnode *collection)
xmlnode *cipher = xmlnode_get_child(node, "RequestedSecurityToken/EncryptedData/CipherData/CipherValue");
xmlnode *secret = xmlnode_get_child(node, "RequestedProofToken/BinarySecret");
+ g_free(nexus->cipher);
nexus->cipher = xmlnode_get_data(cipher);
data = xmlnode_get_data(secret);
+ g_free(nexus->secret);
nexus->secret = (char *)purple_base64_decode(data, NULL);
g_free(data);
@@ -397,7 +399,14 @@ msn_nexus_connect(MsnNexus *nexus)
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);
@@ -506,6 +515,7 @@ nexus_got_update_cb(MsnSoapMessage *req, MsnSoapMessage *resp, gpointer data)
}
g_free(ud);
+ g_free(key);
}
void
diff --git a/libpurple/protocols/msn/notification.c b/libpurple/protocols/msn/notification.c
index 9aef684904..9e43feeb03 100644
--- a/libpurple/protocols/msn/notification.c
+++ b/libpurple/protocols/msn/notification.c
@@ -583,6 +583,7 @@ msn_notification_send_fqy(MsnSession *session,
trans = msn_transaction_new(cmdproc, "FQY", "%d", payload_len);
msn_transaction_set_payload(trans, payload, payload_len);
msn_transaction_set_data(trans, data);
+ msn_transaction_set_data_free(trans, g_free);
msn_cmdproc_send_trans(cmdproc, trans);
}
@@ -621,7 +622,7 @@ update_contact_network(MsnSession *session, const char *passport, MsnNetwork net
user->list_op & MSN_LIST_OP_MASK, network);
payload = xmlnode_to_str(adl_node, &payload_len);
msn_notification_post_adl(session->notification->cmdproc, payload, payload_len);
-
+ g_free(payload);
} else {
purple_debug_error("msn",
"Got FQY update for unknown user %s on network %d.\n",
@@ -669,7 +670,7 @@ msn_notification_dump_contact(MsnSession *session)
"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) {
@@ -839,17 +840,48 @@ adl_error_parse(MsnCmdProc *cmdproc, MsnCommand *cmd, char *payload, size_t len)
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
@@ -877,50 +909,49 @@ adl_error(MsnCmdProc *cmdproc, MsnTransaction *trans, int error)
}
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);
-
- 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);
- }
+ adl = g_strndup(payload, len);
+ reason = g_strdup_printf(_("Unknown error (%d): %s"),
+ error, adl);
+ g_free(adl);
- 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
@@ -962,9 +993,8 @@ fqy_cmd_post(MsnCmdProc *cmdproc, MsnCommand *cmd, char *payload,
if (cmd->trans->data) {
MsnFqyCbData *fqy_data = cmd->trans->data;
fqy_data->cb(session, passport, network, fqy_data->data);
- /* TODO: This leaks, but the server responds to FQY multiple times, so we
- can't free it yet. We need to figure out somewhere else to do so.
- g_free(fqy_data); */
+ /* Don't free fqy_data yet since the server responds to FQY multiple times.
+ It will be freed when cmd->trans is freed. */
}
g_free(passport);
@@ -1068,7 +1098,17 @@ iln_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
/* 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]));
@@ -1098,7 +1138,6 @@ iln_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
}
if (msn_user_set_friendly_name(user, friendly)) {
- serv_got_alias(gc, passport, friendly);
msn_update_contact(session, passport, MSN_UPDATE_DISPLAY, friendly);
}
g_free(friendly);
@@ -1263,7 +1302,6 @@ nln_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
if (msn_user_set_friendly_name(user, friendly))
{
- serv_got_alias(gc, passport, friendly);
msn_update_contact(session, passport, MSN_UPDATE_DISPLAY, friendly);
}
@@ -2097,9 +2135,8 @@ msn_notification_init(void)
msn_table_add_cmd(cbs_table, "fallback", "XFR", xfr_cmd);
- msn_table_add_cmd(cbs_table, NULL, "241", adl_241_error_cmd);
-
msn_table_add_error(cbs_table, "ADL", adl_error);
+ msn_table_add_error(cbs_table, "RML", rml_error);
msn_table_add_error(cbs_table, "FQY", fqy_error);
msn_table_add_error(cbs_table, "USR", usr_error);
diff --git a/libpurple/protocols/msn/oim.c b/libpurple/protocols/msn/oim.c
index ddd36c45ca..a4ab5d2ce0 100644
--- a/libpurple/protocols/msn/oim.c
+++ b/libpurple/protocols/msn/oim.c
@@ -153,7 +153,7 @@ typedef struct _MsnOimRequestData {
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 @@ msn_oim_request_cb(MsnSoapMessage *request, MsnSoapMessage *response,
g_free(data);
}
-static void
+static gboolean
msn_oim_request_helper(MsnOimRequestData *data)
{
MsnSession *session = data->oim->session;
@@ -224,13 +224,13 @@ msn_oim_request_helper(MsnOimRequestData *data)
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_oim_request_helper(MsnOimRequestData *data)
msn_soap_message_new(data->action, xmlnode_copy(data->body)),
data->host, data->url, FALSE,
msn_oim_request_cb, data);
+
+ return FALSE;
}
@@ -373,6 +375,7 @@ msn_oim_send_read_cb(MsnSoapMessage *request, MsnSoapMessage *response,
msg->oim_msg);
g_queue_push_head(oim->send_queue, msg);
msn_oim_send_msg(oim);
+ msg = NULL;
} else {
purple_debug_info("msn",
"Can't find lock key for OIM: %s\n",
@@ -393,6 +396,7 @@ msn_oim_send_read_cb(MsnSoapMessage *request, MsnSoapMessage *response,
purple_debug_info("msn", "Resending OIM: %s\n", msg->oim_msg);
g_queue_push_head(oim->send_queue, msg);
msn_oim_send_msg(oim);
+ msg = NULL;
}
} else {
/* Report the error */
@@ -426,6 +430,9 @@ msn_oim_send_read_cb(MsnSoapMessage *request, MsnSoapMessage *response,
}
}
}
+
+ if (msg)
+ msn_oim_free_send_req(msg);
}
void
diff --git a/libpurple/protocols/msn/servconn.c b/libpurple/protocols/msn/servconn.c
index 1d57865a06..28a9226ca6 100644
--- a/libpurple/protocols/msn/servconn.c
+++ b/libpurple/protocols/msn/servconn.c
@@ -86,7 +86,7 @@ msn_servconn_destroy(MsnServConn *servconn)
if (servconn->tx_handler > 0)
purple_input_remove(servconn->tx_handler);
if (servconn->timeout_handle > 0)
- purple_input_remove(servconn->timeout_handle);
+ purple_timeout_remove(servconn->timeout_handle);
msn_cmdproc_destroy(servconn->cmdproc);
g_free(servconn);
@@ -280,7 +280,7 @@ msn_servconn_disconnect(MsnServConn *servconn)
if (servconn->timeout_handle > 0)
{
- purple_input_remove(servconn->timeout_handle);
+ purple_timeout_remove(servconn->timeout_handle);
servconn->timeout_handle = 0;
}
@@ -299,8 +299,8 @@ msn_servconn_disconnect(MsnServConn *servconn)
static gboolean
servconn_idle_timeout_cb(MsnServConn *servconn)
{
- msn_servconn_disconnect(servconn);
servconn->timeout_handle = 0;
+ msn_servconn_disconnect(servconn);
return FALSE;
}
@@ -308,7 +308,7 @@ static void
servconn_timeout_renew(MsnServConn *servconn)
{
if (servconn->timeout_handle) {
- purple_input_remove(servconn->timeout_handle);
+ purple_timeout_remove(servconn->timeout_handle);
servconn->timeout_handle = 0;
}
@@ -440,11 +440,12 @@ read_cb(gpointer data, gint source, PurpleInputCondition cond)
memcpy(servconn->rx_buf + servconn->rx_len, buf, len + 1);
servconn->rx_len += len;
- msn_servconn_process_data(servconn);
- servconn_timeout_renew(servconn);
+ servconn = msn_servconn_process_data(servconn);
+ if (servconn)
+ servconn_timeout_renew(servconn);
}
-void msn_servconn_process_data(MsnServConn *servconn)
+MsnServConn *msn_servconn_process_data(MsnServConn *servconn)
{
char *cur, *end, *old_rx_buf;
int cur_len;
@@ -503,10 +504,13 @@ void msn_servconn_process_data(MsnServConn *servconn)
servconn->processing = FALSE;
- if (servconn->wasted)
+ if (servconn->wasted) {
msn_servconn_destroy(servconn);
+ servconn = NULL;
+ }
g_free(old_rx_buf);
+ return servconn;
}
#if 0
diff --git a/libpurple/protocols/msn/servconn.h b/libpurple/protocols/msn/servconn.h
index 6023a7cd04..70a9cbb3ad 100644
--- a/libpurple/protocols/msn/servconn.h
+++ b/libpurple/protocols/msn/servconn.h
@@ -178,8 +178,10 @@ void msn_servconn_got_error(MsnServConn *servconn, MsnServConnError error,
* data from the socket.
*
* @param servconn The servconn.
+ *
+ * @return @c NULL if servconn was destroyed, 'servconn' otherwise.
*/
-void msn_servconn_process_data(MsnServConn *servconn);
+MsnServConn *msn_servconn_process_data(MsnServConn *servconn);
/**
* Set a idle timeout fot this servconn
diff --git a/libpurple/protocols/msn/transaction.c b/libpurple/protocols/msn/transaction.c
index 0c490179a3..936f665696 100644
--- a/libpurple/protocols/msn/transaction.c
+++ b/libpurple/protocols/msn/transaction.c
@@ -59,6 +59,9 @@ msn_transaction_destroy(MsnTransaction *trans)
g_free(trans->params);
g_free(trans->payload);
+ if (trans->data_free)
+ trans->data_free(trans->data);
+
#if 0
if (trans->pendent_cmd != NULL)
msn_message_unref(trans->pendent_msg);
@@ -165,6 +168,12 @@ msn_transaction_set_data(MsnTransaction *trans, void *data)
trans->data = data;
}
+void msn_transaction_set_data_free(MsnTransaction *trans, GDestroyNotify fn)
+{
+ g_return_if_fail(trans != NULL);
+ trans->data_free = fn;
+}
+
void
msn_transaction_add_cb(MsnTransaction *trans, char *answer,
MsnTransCb cb)
diff --git a/libpurple/protocols/msn/transaction.h b/libpurple/protocols/msn/transaction.h
index 98bffc61cc..61181dd93d 100644
--- a/libpurple/protocols/msn/transaction.h
+++ b/libpurple/protocols/msn/transaction.h
@@ -48,6 +48,8 @@ struct _MsnTransaction
guint timer;
void *data; /**< The data to be used on the different callbacks. */
+ GDestroyNotify data_free; /**< The function to free 'data', or @c NULL */
+
GHashTable *callbacks;
gboolean has_custom_callbacks;
MsnErrorCb error_cb;
@@ -71,6 +73,7 @@ void msn_transaction_unqueue_cmd(MsnTransaction *trans, MsnCmdProc *cmdproc);
void msn_transaction_set_payload(MsnTransaction *trans,
const char *payload, int payload_len);
void msn_transaction_set_data(MsnTransaction *trans, void *data);
+void msn_transaction_set_data_free(MsnTransaction *trans, GDestroyNotify fn);
void msn_transaction_add_cb(MsnTransaction *trans, char *answer,
MsnTransCb cb);
void msn_transaction_set_error_cb(MsnTransaction *trans, MsnErrorCb cb);
diff --git a/libpurple/protocols/msn/user.c b/libpurple/protocols/msn/user.c
index de4e4aabd0..741989b616 100644
--- a/libpurple/protocols/msn/user.c
+++ b/libpurple/protocols/msn/user.c
@@ -183,12 +183,15 @@ msn_user_set_friendly_name(MsnUser *user, const char *name)
{
g_return_val_if_fail(user != NULL, FALSE);
- if (user->friendly_name && name && !strcmp(user->friendly_name, name))
+ if (user->friendly_name && name && (!strcmp(user->friendly_name, name) ||
+ !strcmp(user->passport, name)))
return FALSE;
g_free(user->friendly_name);
user->friendly_name = g_strdup(name);
+ serv_got_alias(purple_account_get_connection(user->userlist->session->account),
+ user->passport, name);
return TRUE;
}
diff --git a/libpurple/protocols/msn/userlist.c b/libpurple/protocols/msn/userlist.c
index 5e1135292b..78ef9f28a4 100644
--- a/libpurple/protocols/msn/userlist.c
+++ b/libpurple/protocols/msn/userlist.c
@@ -539,7 +539,7 @@ msn_userlist_add_buddy(MsnUserList *userlist, const char *who, const char *group
purple_debug_info("msn", "Add user: %s to group: %s\n", who, new_group_name);
- if (!purple_email_is_valid(who))
+ if (!msn_email_is_valid(who))
{
/* only notify the user about problems adding to the friends list
* maybe we should do something else for other lists, but it probably
diff --git a/libpurple/protocols/mxit/Makefile.am b/libpurple/protocols/mxit/Makefile.am
new file mode 100644
index 0000000000..9d89cc946e
--- /dev/null
+++ b/libpurple/protocols/mxit/Makefile.am
@@ -0,0 +1,63 @@
+EXTRA_DIST = \
+ Makefile.mingw
+
+pkgdir = $(libdir)/purple-$(PURPLE_MAJOR_VERSION)
+
+MXITSOURCES = \
+ actions.c \
+ actions.h \
+ aes.c \
+ aes.h \
+ chunk.c \
+ chunk.h \
+ cipher.c \
+ cipher.h \
+ filexfer.c \
+ filexfer.h \
+ formcmds.c \
+ formcmds.h \
+ http.c \
+ http.h \
+ login.c \
+ login.h \
+ markup.c \
+ markup.h \
+ multimx.c \
+ multimx.h \
+ mxit.c \
+ mxit.h \
+ profile.c \
+ profile.h \
+ protocol.c \
+ protocol.h \
+ roster.c \
+ roster.h \
+ splashscreen.c \
+ splashscreen.h
+
+
+AM_CFLAGS = $(st)
+
+libmxit_la_LDFLAGS = -module -avoid-version
+
+if STATIC_MXIT
+
+st = -DPURPLE_STATIC_PRPL
+noinst_LTLIBRARIES = libmxit.la
+libmxit_la_SOURCES = $(MXITSOURCES)
+libmxit_la_CFLAGS = $(AM_CFLAGS)
+
+else
+
+st =
+pkg_LTLIBRARIES = libmxit.la
+libmxit_la_SOURCES = $(MXITSOURCES)
+libmxit_la_LIBADD = $(GLIB_LIBS)
+
+endif
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/libpurple \
+ -I$(top_builddir)/libpurple \
+ $(GLIB_CFLAGS) \
+ $(DEBUG_CFLAGS)
diff --git a/libpurple/protocols/mxit/Makefile.mingw b/libpurple/protocols/mxit/Makefile.mingw
new file mode 100644
index 0000000000..00d0b6db61
--- /dev/null
+++ b/libpurple/protocols/mxit/Makefile.mingw
@@ -0,0 +1,91 @@
+#
+# Makefile.mingw
+#
+# Description: Makefile for win32 (mingw) version of libmxit
+#
+
+PIDGIN_TREE_TOP := ../../..
+include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
+
+TARGET = libmxit
+TYPE = PLUGIN
+
+# Static or Plugin...
+ifeq ($(TYPE),STATIC)
+ DEFINES += -DSTATIC
+ DLL_INSTALL_DIR = $(PURPLE_INSTALL_DIR)
+else
+ifeq ($(TYPE),PLUGIN)
+ DLL_INSTALL_DIR = $(PURPLE_INSTALL_PLUGINS_DIR)
+endif
+endif
+
+##
+## INCLUDE PATHS
+##
+INCLUDE_PATHS += -I. \
+ -I$(GTK_TOP)/include \
+ -I$(GTK_TOP)/include/glib-2.0 \
+ -I$(GTK_TOP)/lib/glib-2.0/include \
+ -I$(PURPLE_TOP) \
+ -I$(PURPLE_TOP)/win32 \
+ -I$(PIDGIN_TREE_TOP)
+
+LIB_PATHS += -L$(GTK_TOP)/lib \
+ -L$(PURPLE_TOP)
+
+##
+## SOURCES, OBJECTS
+##
+C_SRC = actions.c \
+ aes.c \
+ chunk.c \
+ cipher.c \
+ filexfer.c \
+ formcmds.c \
+ http.c \
+ login.c \
+ markup.c \
+ multimx.c \
+ mxit.c \
+ profile.c \
+ protocol.c \
+ roster.c \
+ splashscreen.c
+
+OBJECTS = $(C_SRC:%.c=%.o)
+
+##
+## LIBRARIES
+##
+LIBS = \
+ -lglib-2.0 \
+ -lintl \
+ -lws2_32 \
+ -lpurple
+
+include $(PIDGIN_COMMON_RULES)
+
+##
+## TARGET DEFINITIONS
+##
+.PHONY: all install clean
+
+all: $(TARGET).dll
+
+install: all $(DLL_INSTALL_DIR)
+ cp $(TARGET).dll $(DLL_INSTALL_DIR)
+
+$(OBJECTS): $(PURPLE_CONFIG_H)
+
+$(TARGET).dll: $(PURPLE_DLL).a $(OBJECTS)
+ $(CC) -shared $(OBJECTS) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -o $(TARGET).dll
+
+##
+## CLEAN RULES
+##
+clean:
+ rm -f $(OBJECTS)
+ rm -f $(TARGET).dll
+
+include $(PIDGIN_COMMON_TARGETS)
diff --git a/libpurple/protocols/mxit/actions.c b/libpurple/protocols/mxit/actions.c
new file mode 100644
index 0000000000..eb231b4632
--- /dev/null
+++ b/libpurple/protocols/mxit/actions.c
@@ -0,0 +1,437 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- handle MXit plugin actions --
+ *
+ * Pieter Loubser <libpurple@mxit.com>
+ *
+ * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
+ * <http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "purple.h"
+
+#include "protocol.h"
+#include "mxit.h"
+#include "roster.h"
+#include "actions.h"
+#include "splashscreen.h"
+#include "cipher.h"
+#include "profile.h"
+
+
+/* MXit Moods */
+static const char* moods[] = {
+ /* 0 */ "None",
+ /* 1 */ "Angry",
+ /* 2 */ "Excited",
+ /* 3 */ "Grumpy",
+ /* 4 */ "Happy",
+ /* 5 */ "In Love",
+ /* 6 */ "Invincible",
+ /* 7 */ "Sad",
+ /* 8 */ "Hot",
+ /* 9 */ "Sick",
+ /* 10 */ "Sleepy"
+};
+
+
+/*------------------------------------------------------------------------
+ * The user has selected to change their current mood.
+ *
+ * @param gc The connection object
+ * @param fields The fields from the request pop-up
+ */
+static void mxit_cb_set_mood( PurpleConnection* gc, PurpleRequestFields* fields )
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+ int mood = purple_request_fields_get_choice( fields, "mood" );
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_set_mood (%i)\n", mood );
+
+ if ( !PURPLE_CONNECTION_IS_VALID( gc ) ) {
+ purple_debug_error( MXIT_PLUGIN_ID, "Unable to set mood; account offline.\n" );
+ return;
+ }
+
+ /* Save the new mood in session */
+ session->mood = mood;
+
+ /* now send the update to MXit */
+ mxit_send_mood( session, mood );
+}
+
+
+/*------------------------------------------------------------------------
+ * Create and display the mood selection window to the user.
+ *
+ * @param action The action object
+ */
+static void mxit_cb_action_mood( PurplePluginAction* action )
+{
+ PurpleConnection* gc = (PurpleConnection*) action->context;
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+
+ PurpleRequestFields* fields = NULL;
+ PurpleRequestFieldGroup* group = NULL;
+ PurpleRequestField* field = NULL;
+ unsigned int i = 0;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_action_mood\n" );
+
+ fields = purple_request_fields_new();
+ group = purple_request_field_group_new( NULL );
+ purple_request_fields_add_group( fields, group );
+
+ /* show current mood */
+ field = purple_request_field_string_new( "current", _( "Current Mood" ), _( moods[session->mood] ), FALSE );
+ purple_request_field_string_set_editable( field, FALSE ); /* current mood field is not editable */
+ purple_request_field_group_add_field( group, field );
+
+ /* add all moods to list */
+ field = purple_request_field_choice_new( "mood", _( "New Mood" ), 0 );
+ for ( i = 0; i < ARRAY_SIZE( moods ); i++ ) {
+ purple_request_field_choice_add( field, _( moods[i] ) );
+ }
+ purple_request_field_set_required( field, TRUE );
+ purple_request_field_choice_set_default_value( field, session->mood );
+ purple_request_field_group_add_field( group, field );
+
+ /* (reference: "libpurple/request.h") */
+ purple_request_fields( gc, _( "Mood" ), _( "Change your Mood" ), _( "How do you feel right now?" ), fields, _( "Set" ),
+ G_CALLBACK( mxit_cb_set_mood ), _( "Cancel" ), NULL, purple_connection_get_account( gc ), NULL, NULL, gc );
+}
+
+
+/*------------------------------------------------------------------------
+ * The user has selected to change their profile.
+ *
+ * @param gc The connection object
+ * @param fields The fields from the request pop-up
+ */
+static void mxit_cb_set_profile( PurpleConnection* gc, PurpleRequestFields* fields )
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+ PurpleRequestField* field = NULL;
+ const char* pin = NULL;
+ const char* pin2 = NULL;
+ const char* name = NULL;
+ const char* bday = NULL;
+ const char* err = NULL;
+ int len;
+ int i;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_set_profile\n" );
+
+ if ( !PURPLE_CONNECTION_IS_VALID( gc ) ) {
+ purple_debug_error( MXIT_PLUGIN_ID, "Unable to update profile; account offline.\n" );
+ return;
+ }
+
+ /* validate pin */
+ pin = purple_request_fields_get_string( fields, "pin" );
+ if ( !pin ) {
+ err = "The PIN you entered is invalid.";
+ goto out;
+ }
+ len = strlen( pin );
+ if ( ( len < 4 ) || ( len > 10 ) ) {
+ err = "The PIN you entered has an invalid length [4-10].";
+ goto out;
+ }
+ for ( i = 0; i < len; i++ ) {
+ if ( !g_ascii_isdigit( pin[i] ) ) {
+ err = "The PIN is invalid. It should only consist of digits [0-9].";
+ goto out;
+ }
+ }
+ pin2 = purple_request_fields_get_string( fields, "pin2" );
+ if ( ( !pin2 ) || ( strcmp( pin, pin2 ) != 0 ) ) {
+ err = "The two PINs you entered does not match.";
+ goto out;
+ }
+
+ /* validate name */
+ name = purple_request_fields_get_string( fields, "name" );
+ if ( ( !name ) || ( strlen( name ) < 3 ) ) {
+ err = "The name you entered is invalid.";
+ goto out;
+ }
+
+ /* validate birthdate */
+ bday = purple_request_fields_get_string( fields, "bday" );
+ if ( ( !bday ) || ( strlen( bday ) < 10 ) || ( !validateDate( bday ) ) ) {
+ err = "The birthday you entered is invalid. The correct format is: 'YYYY-MM-DD'.";
+ goto out;
+ }
+
+out:
+ if ( !err ) {
+ struct MXitProfile* profile = session->profile;
+ GString* attributes = g_string_sized_new( 128 );
+ char attrib[512];
+ unsigned int acount = 0;
+
+ /* all good, so we can now update the profile */
+
+ /* update pin */
+ purple_account_set_password( session->acc, pin );
+ g_free( session->encpwd );
+ session->encpwd = mxit_encrypt_password( session );
+
+ /* update name */
+ g_strlcpy( profile->nickname, name, sizeof( profile->nickname ) );
+ g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_FULLNAME, CP_PROF_TYPE_UTF8, profile->nickname );
+ g_string_append( attributes, attrib );
+ acount++;
+
+ /* update hidden */
+ field = purple_request_fields_get_field( fields, "hidden" );
+ profile->hidden = purple_request_field_bool_get_value( field );
+ g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_HIDENUMBER, CP_PROF_TYPE_BOOL, ( profile->hidden ) ? "1" : "0" );
+ g_string_append( attributes, attrib );
+ acount++;
+
+ /* update birthday */
+ strcpy( profile->birthday, bday );
+ g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_BIRTHDATE, CP_PROF_TYPE_UTF8, profile->birthday );
+ g_string_append( attributes, attrib );
+ acount++;
+
+ /* update gender */
+ if ( purple_request_fields_get_choice( fields, "male" ) == 0 )
+ profile->male = FALSE;
+ else
+ profile->male = TRUE;
+ g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_GENDER, CP_PROF_TYPE_BOOL, ( profile->male ) ? "1" : "0" );
+ g_string_append( attributes, attrib );
+ acount++;
+
+ /* update title */
+ name = purple_request_fields_get_string( fields, "title" );
+ if ( !name )
+ profile->title[0] = '\0';
+ else
+ strcpy( profile->title, name );
+ g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_TITLE, CP_PROF_TYPE_UTF8, profile->title );
+ g_string_append( attributes, attrib );
+ acount++;
+
+ /* update firstname */
+ name = purple_request_fields_get_string( fields, "firstname" );
+ if ( !name )
+ profile->firstname[0] = '\0';
+ else
+ strcpy( profile->firstname, name );
+ g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_FIRSTNAME, CP_PROF_TYPE_UTF8, profile->firstname );
+ g_string_append( attributes, attrib );
+ acount++;
+
+ /* update lastname */
+ name = purple_request_fields_get_string( fields, "lastname" );
+ if ( !name )
+ profile->lastname[0] = '\0';
+ else
+ strcpy( profile->lastname, name );
+ g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_LASTNAME, CP_PROF_TYPE_UTF8, profile->lastname );
+ g_string_append( attributes, attrib );
+ acount++;
+
+ /* update email address */
+ name = purple_request_fields_get_string( fields, "email" );
+ if ( !name )
+ profile->email[0] = '\0';
+ else
+ strcpy( profile->email, name );
+ g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_EMAIL, CP_PROF_TYPE_UTF8, profile->email );
+ g_string_append( attributes, attrib );
+ acount++;
+
+ /* update mobile number */
+ name = purple_request_fields_get_string( fields, "mobilenumber" );
+ if ( !name )
+ profile->mobilenr[0] = '\0';
+ else
+ strcpy( profile->mobilenr, name );
+ g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_MOBILENR, CP_PROF_TYPE_UTF8, profile->mobilenr );
+ g_string_append( attributes, attrib );
+ acount++;
+
+ /* send the profile update to MXit */
+ mxit_send_extprofile_update( session, session->encpwd, acount, attributes->str );
+ g_string_free( attributes, TRUE );
+ }
+ else {
+ /* show error to user */
+ mxit_popup( PURPLE_NOTIFY_MSG_ERROR, _( "Profile Update Error" ), _( err ) );
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Display and update the user's profile.
+ *
+ * @param action The action object
+ */
+static void mxit_cb_action_profile( PurplePluginAction* action )
+{
+ PurpleConnection* gc = (PurpleConnection*) action->context;
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+ struct MXitProfile* profile = session->profile;
+
+ PurpleRequestFields* fields = NULL;
+ PurpleRequestFieldGroup* group = NULL;
+ PurpleRequestField* field = NULL;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_action_profile\n" );
+
+ /* ensure that we actually have the user's profile information */
+ if ( !profile ) {
+ /* no profile information yet, so we cannot update */
+ mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Profile" ), _( "Your profile information is not yet retrieved. Please try again later." ) );
+ return;
+ }
+
+ fields = purple_request_fields_new();
+ group = purple_request_field_group_new( NULL );
+ purple_request_fields_add_group( fields, group );
+
+ /* pin */
+ field = purple_request_field_string_new( "pin", _( "PIN" ), session->acc->password, FALSE );
+ purple_request_field_string_set_masked( field, TRUE );
+ purple_request_field_group_add_field( group, field );
+ field = purple_request_field_string_new( "pin2", _( "Verify PIN" ), session->acc->password, FALSE );
+ purple_request_field_string_set_masked( field, TRUE );
+ purple_request_field_group_add_field( group, field );
+
+ /* display name */
+ field = purple_request_field_string_new( "name", _( "Display Name" ), profile->nickname, FALSE );
+ purple_request_field_group_add_field( group, field );
+
+ /* birthday */
+ field = purple_request_field_string_new( "bday", _( "Birthday" ), profile->birthday, FALSE );
+ purple_request_field_group_add_field( group, field );
+
+ /* gender */
+ field = purple_request_field_choice_new( "male", _( "Gender" ), ( profile->male ) ? 1 : 0 );
+ purple_request_field_choice_add( field, _( "Female" ) ); /* 0 */
+ purple_request_field_choice_add( field, _( "Male" ) ); /* 1 */
+ purple_request_field_group_add_field( group, field );
+
+ /* hidden */
+ field = purple_request_field_bool_new( "hidden", _( "Hide my number" ), profile->hidden );
+ purple_request_field_group_add_field( group, field );
+
+ /* title */
+ field = purple_request_field_string_new( "title", _( "Title" ), profile->title, FALSE );
+ purple_request_field_group_add_field( group, field );
+
+ /* first name */
+ field = purple_request_field_string_new( "firstname", _( "First Name" ), profile->firstname, FALSE );
+ purple_request_field_group_add_field( group, field );
+
+ /* last name */
+ field = purple_request_field_string_new( "lastname", _( "Last Name" ), profile->lastname, FALSE );
+ purple_request_field_group_add_field( group, field );
+
+ /* email */
+ field = purple_request_field_string_new( "email", _( "Email" ), profile->email, FALSE );
+ purple_request_field_group_add_field( group, field );
+
+ /* mobile number */
+ field = purple_request_field_string_new( "mobilenumber", _( "Mobile Number" ), profile->mobilenr, FALSE );
+ purple_request_field_group_add_field( group, field );
+
+ /* (reference: "libpurple/request.h") */
+ purple_request_fields( gc, _( "Profile" ), _( "Update your Profile" ), _( "Here you can update your MXit profile" ), fields, _( "Set" ),
+ G_CALLBACK( mxit_cb_set_profile ), _( "Cancel" ), NULL, purple_connection_get_account( gc ), NULL, NULL, gc );
+}
+
+
+/*------------------------------------------------------------------------
+ * Display the current splash-screen, or a notification pop-up if one is not available.
+ *
+ * @param action The action object
+ */
+static void mxit_cb_action_splash( PurplePluginAction* action )
+{
+ PurpleConnection* gc = (PurpleConnection*) action->context;
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+
+ if ( splash_current( session ) != NULL )
+ splash_display( session );
+ else
+ mxit_popup( PURPLE_NOTIFY_MSG_INFO, _( "View Splash" ), _( "There is no splash-screen currently available" ) );
+}
+
+
+/*------------------------------------------------------------------------
+ * Display info about the plugin.
+ *
+ * @param action The action object
+ */
+static void mxit_cb_action_about( PurplePluginAction* action )
+{
+ char version[256];
+
+ g_snprintf( version, sizeof( version ), "MXit libPurple Plugin v%s\n"
+ "MXit Client Protocol v%s\n\n"
+ "Author:\nPieter Loubser\n\n"
+ "Contributors:\nAndrew Victor\n\n"
+ "Testers:\nBraeme Le Roux\n\n",
+ MXIT_PLUGIN_VERSION, MXIT_CP_RELEASE );
+
+ mxit_popup( PURPLE_NOTIFY_MSG_INFO, _( "About" ), version );
+}
+
+
+/*------------------------------------------------------------------------
+ * Associate actions with the MXit plugin.
+ *
+ * @param plugin The MXit protocol plugin
+ * @param context The connection context (if available)
+ * @return The list of plugin actions
+ */
+GList* mxit_actions( PurplePlugin* plugin, gpointer context )
+{
+ PurplePluginAction* action = NULL;
+ GList* m = NULL;
+
+ /* display / change mood */
+ action = purple_plugin_action_new( _( "Change Mood..." ), mxit_cb_action_mood );
+ m = g_list_append( m, action );
+
+ /* display / change profile */
+ action = purple_plugin_action_new( _( "Change Profile..." ), mxit_cb_action_profile );
+ m = g_list_append( m, action );
+
+ /* display splash-screen */
+ action = purple_plugin_action_new( _( "View Splash..." ), mxit_cb_action_splash );
+ m = g_list_append( m, action );
+
+ /* display plugin version */
+ action = purple_plugin_action_new( _( "About..." ), mxit_cb_action_about );
+ m = g_list_append( m, action );
+
+ return m;
+}
+
diff --git a/libpurple/protocols/mxit/actions.h b/libpurple/protocols/mxit/actions.h
new file mode 100644
index 0000000000..0157e17c96
--- /dev/null
+++ b/libpurple/protocols/mxit/actions.h
@@ -0,0 +1,34 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- handle MXit plugin actions --
+ *
+ * Pieter Loubser <libpurple@mxit.com>
+ *
+ * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
+ * <http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#ifndef _MXIT_ACTIONS_H_
+#define _MXIT_ACTIONS_H_
+
+
+/* callbacks */
+GList* mxit_actions( PurplePlugin* plugin, gpointer context );
+
+
+#endif /* _MXIT_ACTIONS_H_ */
diff --git a/libpurple/protocols/mxit/aes.c b/libpurple/protocols/mxit/aes.c
new file mode 100644
index 0000000000..2a2702b1a8
--- /dev/null
+++ b/libpurple/protocols/mxit/aes.c
@@ -0,0 +1,405 @@
+
+// advanced encryption standard
+// author: karl malbrain, malbrain@yahoo.com
+
+/*
+This work, including the source code, documentation
+and related data, is placed into the public domain.
+
+The orginal author is Karl Malbrain.
+
+THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY
+OF ANY KIND, NOT EVEN THE IMPLIED WARRANTY OF
+MERCHANTABILITY. THE AUTHOR OF THIS SOFTWARE,
+ASSUMES _NO_ RESPONSIBILITY FOR ANY CONSEQUENCE
+RESULTING FROM THE USE, MODIFICATION, OR
+REDISTRIBUTION OF THIS SOFTWARE.
+*/
+
+#include <string.h>
+#include <memory.h>
+
+#include "aes.h"
+
+// AES only supports Nb=4
+#define Nb 4 // number of columns in the state & expanded key
+
+#define Nk 4 // number of columns in a key
+#define Nr 10 // number of rounds in encryption
+
+static uchar Sbox[256] = { // forward s-box
+0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
+0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
+0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
+0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
+0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
+0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
+0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
+0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
+0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
+0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
+0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
+0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
+0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
+0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
+0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
+0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16};
+
+static uchar InvSbox[256] = { // inverse s-box
+0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
+0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
+0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
+0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
+0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
+0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
+0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
+0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
+0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
+0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
+0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
+0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
+0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
+0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
+0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
+0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d};
+
+// combined Xtimes2[Sbox[]]
+static uchar Xtime2Sbox[256] = {
+0xc6, 0xf8, 0xee, 0xf6, 0xff, 0xd6, 0xde, 0x91, 0x60, 0x02, 0xce, 0x56, 0xe7, 0xb5, 0x4d, 0xec,
+0x8f, 0x1f, 0x89, 0xfa, 0xef, 0xb2, 0x8e, 0xfb, 0x41, 0xb3, 0x5f, 0x45, 0x23, 0x53, 0xe4, 0x9b,
+0x75, 0xe1, 0x3d, 0x4c, 0x6c, 0x7e, 0xf5, 0x83, 0x68, 0x51, 0xd1, 0xf9, 0xe2, 0xab, 0x62, 0x2a,
+0x08, 0x95, 0x46, 0x9d, 0x30, 0x37, 0x0a, 0x2f, 0x0e, 0x24, 0x1b, 0xdf, 0xcd, 0x4e, 0x7f, 0xea,
+0x12, 0x1d, 0x58, 0x34, 0x36, 0xdc, 0xb4, 0x5b, 0xa4, 0x76, 0xb7, 0x7d, 0x52, 0xdd, 0x5e, 0x13,
+0xa6, 0xb9, 0x00, 0xc1, 0x40, 0xe3, 0x79, 0xb6, 0xd4, 0x8d, 0x67, 0x72, 0x94, 0x98, 0xb0, 0x85,
+0xbb, 0xc5, 0x4f, 0xed, 0x86, 0x9a, 0x66, 0x11, 0x8a, 0xe9, 0x04, 0xfe, 0xa0, 0x78, 0x25, 0x4b,
+0xa2, 0x5d, 0x80, 0x05, 0x3f, 0x21, 0x70, 0xf1, 0x63, 0x77, 0xaf, 0x42, 0x20, 0xe5, 0xfd, 0xbf,
+0x81, 0x18, 0x26, 0xc3, 0xbe, 0x35, 0x88, 0x2e, 0x93, 0x55, 0xfc, 0x7a, 0xc8, 0xba, 0x32, 0xe6,
+0xc0, 0x19, 0x9e, 0xa3, 0x44, 0x54, 0x3b, 0x0b, 0x8c, 0xc7, 0x6b, 0x28, 0xa7, 0xbc, 0x16, 0xad,
+0xdb, 0x64, 0x74, 0x14, 0x92, 0x0c, 0x48, 0xb8, 0x9f, 0xbd, 0x43, 0xc4, 0x39, 0x31, 0xd3, 0xf2,
+0xd5, 0x8b, 0x6e, 0xda, 0x01, 0xb1, 0x9c, 0x49, 0xd8, 0xac, 0xf3, 0xcf, 0xca, 0xf4, 0x47, 0x10,
+0x6f, 0xf0, 0x4a, 0x5c, 0x38, 0x57, 0x73, 0x97, 0xcb, 0xa1, 0xe8, 0x3e, 0x96, 0x61, 0x0d, 0x0f,
+0xe0, 0x7c, 0x71, 0xcc, 0x90, 0x06, 0xf7, 0x1c, 0xc2, 0x6a, 0xae, 0x69, 0x17, 0x99, 0x3a, 0x27,
+0xd9, 0xeb, 0x2b, 0x22, 0xd2, 0xa9, 0x07, 0x33, 0x2d, 0x3c, 0x15, 0xc9, 0x87, 0xaa, 0x50, 0xa5,
+0x03, 0x59, 0x09, 0x1a, 0x65, 0xd7, 0x84, 0xd0, 0x82, 0x29, 0x5a, 0x1e, 0x7b, 0xa8, 0x6d, 0x2c
+};
+
+// combined Xtimes3[Sbox[]]
+static uchar Xtime3Sbox[256] = {
+0xa5, 0x84, 0x99, 0x8d, 0x0d, 0xbd, 0xb1, 0x54, 0x50, 0x03, 0xa9, 0x7d, 0x19, 0x62, 0xe6, 0x9a,
+0x45, 0x9d, 0x40, 0x87, 0x15, 0xeb, 0xc9, 0x0b, 0xec, 0x67, 0xfd, 0xea, 0xbf, 0xf7, 0x96, 0x5b,
+0xc2, 0x1c, 0xae, 0x6a, 0x5a, 0x41, 0x02, 0x4f, 0x5c, 0xf4, 0x34, 0x08, 0x93, 0x73, 0x53, 0x3f,
+0x0c, 0x52, 0x65, 0x5e, 0x28, 0xa1, 0x0f, 0xb5, 0x09, 0x36, 0x9b, 0x3d, 0x26, 0x69, 0xcd, 0x9f,
+0x1b, 0x9e, 0x74, 0x2e, 0x2d, 0xb2, 0xee, 0xfb, 0xf6, 0x4d, 0x61, 0xce, 0x7b, 0x3e, 0x71, 0x97,
+0xf5, 0x68, 0x00, 0x2c, 0x60, 0x1f, 0xc8, 0xed, 0xbe, 0x46, 0xd9, 0x4b, 0xde, 0xd4, 0xe8, 0x4a,
+0x6b, 0x2a, 0xe5, 0x16, 0xc5, 0xd7, 0x55, 0x94, 0xcf, 0x10, 0x06, 0x81, 0xf0, 0x44, 0xba, 0xe3,
+0xf3, 0xfe, 0xc0, 0x8a, 0xad, 0xbc, 0x48, 0x04, 0xdf, 0xc1, 0x75, 0x63, 0x30, 0x1a, 0x0e, 0x6d,
+0x4c, 0x14, 0x35, 0x2f, 0xe1, 0xa2, 0xcc, 0x39, 0x57, 0xf2, 0x82, 0x47, 0xac, 0xe7, 0x2b, 0x95,
+0xa0, 0x98, 0xd1, 0x7f, 0x66, 0x7e, 0xab, 0x83, 0xca, 0x29, 0xd3, 0x3c, 0x79, 0xe2, 0x1d, 0x76,
+0x3b, 0x56, 0x4e, 0x1e, 0xdb, 0x0a, 0x6c, 0xe4, 0x5d, 0x6e, 0xef, 0xa6, 0xa8, 0xa4, 0x37, 0x8b,
+0x32, 0x43, 0x59, 0xb7, 0x8c, 0x64, 0xd2, 0xe0, 0xb4, 0xfa, 0x07, 0x25, 0xaf, 0x8e, 0xe9, 0x18,
+0xd5, 0x88, 0x6f, 0x72, 0x24, 0xf1, 0xc7, 0x51, 0x23, 0x7c, 0x9c, 0x21, 0xdd, 0xdc, 0x86, 0x85,
+0x90, 0x42, 0xc4, 0xaa, 0xd8, 0x05, 0x01, 0x12, 0xa3, 0x5f, 0xf9, 0xd0, 0x91, 0x58, 0x27, 0xb9,
+0x38, 0x13, 0xb3, 0x33, 0xbb, 0x70, 0x89, 0xa7, 0xb6, 0x22, 0x92, 0x20, 0x49, 0xff, 0x78, 0x7a,
+0x8f, 0xf8, 0x80, 0x17, 0xda, 0x31, 0xc6, 0xb8, 0xc3, 0xb0, 0x77, 0x11, 0xcb, 0xfc, 0xd6, 0x3a
+};
+
+// modular multiplication tables
+// based on:
+
+// Xtime2[x] = (x & 0x80 ? 0x1b : 0) ^ (x + x)
+// Xtime3[x] = x^Xtime2[x];
+
+#if 0
+static uchar Xtime2[256] = {
+0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e,
+0x20, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2c, 0x2e, 0x30, 0x32, 0x34, 0x36, 0x38, 0x3a, 0x3c, 0x3e,
+0x40, 0x42, 0x44, 0x46, 0x48, 0x4a, 0x4c, 0x4e, 0x50, 0x52, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e,
+0x60, 0x62, 0x64, 0x66, 0x68, 0x6a, 0x6c, 0x6e, 0x70, 0x72, 0x74, 0x76, 0x78, 0x7a, 0x7c, 0x7e,
+0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c, 0x8e, 0x90, 0x92, 0x94, 0x96, 0x98, 0x9a, 0x9c, 0x9e,
+0xa0, 0xa2, 0xa4, 0xa6, 0xa8, 0xaa, 0xac, 0xae, 0xb0, 0xb2, 0xb4, 0xb6, 0xb8, 0xba, 0xbc, 0xbe,
+0xc0, 0xc2, 0xc4, 0xc6, 0xc8, 0xca, 0xcc, 0xce, 0xd0, 0xd2, 0xd4, 0xd6, 0xd8, 0xda, 0xdc, 0xde,
+0xe0, 0xe2, 0xe4, 0xe6, 0xe8, 0xea, 0xec, 0xee, 0xf0, 0xf2, 0xf4, 0xf6, 0xf8, 0xfa, 0xfc, 0xfe,
+0x1b, 0x19, 0x1f, 0x1d, 0x13, 0x11, 0x17, 0x15, 0x0b, 0x09, 0x0f, 0x0d, 0x03, 0x01, 0x07, 0x05,
+0x3b, 0x39, 0x3f, 0x3d, 0x33, 0x31, 0x37, 0x35, 0x2b, 0x29, 0x2f, 0x2d, 0x23, 0x21, 0x27, 0x25,
+0x5b, 0x59, 0x5f, 0x5d, 0x53, 0x51, 0x57, 0x55, 0x4b, 0x49, 0x4f, 0x4d, 0x43, 0x41, 0x47, 0x45,
+0x7b, 0x79, 0x7f, 0x7d, 0x73, 0x71, 0x77, 0x75, 0x6b, 0x69, 0x6f, 0x6d, 0x63, 0x61, 0x67, 0x65,
+0x9b, 0x99, 0x9f, 0x9d, 0x93, 0x91, 0x97, 0x95, 0x8b, 0x89, 0x8f, 0x8d, 0x83, 0x81, 0x87, 0x85,
+0xbb, 0xb9, 0xbf, 0xbd, 0xb3, 0xb1, 0xb7, 0xb5, 0xab, 0xa9, 0xaf, 0xad, 0xa3, 0xa1, 0xa7, 0xa5,
+0xdb, 0xd9, 0xdf, 0xdd, 0xd3, 0xd1, 0xd7, 0xd5, 0xcb, 0xc9, 0xcf, 0xcd, 0xc3, 0xc1, 0xc7, 0xc5,
+0xfb, 0xf9, 0xff, 0xfd, 0xf3, 0xf1, 0xf7, 0xf5, 0xeb, 0xe9, 0xef, 0xed, 0xe3, 0xe1, 0xe7, 0xe5};
+#endif
+
+static uchar Xtime9[256] = {
+0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77,
+0x90, 0x99, 0x82, 0x8b, 0xb4, 0xbd, 0xa6, 0xaf, 0xd8, 0xd1, 0xca, 0xc3, 0xfc, 0xf5, 0xee, 0xe7,
+0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04, 0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c,
+0xab, 0xa2, 0xb9, 0xb0, 0x8f, 0x86, 0x9d, 0x94, 0xe3, 0xea, 0xf1, 0xf8, 0xc7, 0xce, 0xd5, 0xdc,
+0x76, 0x7f, 0x64, 0x6d, 0x52, 0x5b, 0x40, 0x49, 0x3e, 0x37, 0x2c, 0x25, 0x1a, 0x13, 0x08, 0x01,
+0xe6, 0xef, 0xf4, 0xfd, 0xc2, 0xcb, 0xd0, 0xd9, 0xae, 0xa7, 0xbc, 0xb5, 0x8a, 0x83, 0x98, 0x91,
+0x4d, 0x44, 0x5f, 0x56, 0x69, 0x60, 0x7b, 0x72, 0x05, 0x0c, 0x17, 0x1e, 0x21, 0x28, 0x33, 0x3a,
+0xdd, 0xd4, 0xcf, 0xc6, 0xf9, 0xf0, 0xeb, 0xe2, 0x95, 0x9c, 0x87, 0x8e, 0xb1, 0xb8, 0xa3, 0xaa,
+0xec, 0xe5, 0xfe, 0xf7, 0xc8, 0xc1, 0xda, 0xd3, 0xa4, 0xad, 0xb6, 0xbf, 0x80, 0x89, 0x92, 0x9b,
+0x7c, 0x75, 0x6e, 0x67, 0x58, 0x51, 0x4a, 0x43, 0x34, 0x3d, 0x26, 0x2f, 0x10, 0x19, 0x02, 0x0b,
+0xd7, 0xde, 0xc5, 0xcc, 0xf3, 0xfa, 0xe1, 0xe8, 0x9f, 0x96, 0x8d, 0x84, 0xbb, 0xb2, 0xa9, 0xa0,
+0x47, 0x4e, 0x55, 0x5c, 0x63, 0x6a, 0x71, 0x78, 0x0f, 0x06, 0x1d, 0x14, 0x2b, 0x22, 0x39, 0x30,
+0x9a, 0x93, 0x88, 0x81, 0xbe, 0xb7, 0xac, 0xa5, 0xd2, 0xdb, 0xc0, 0xc9, 0xf6, 0xff, 0xe4, 0xed,
+0x0a, 0x03, 0x18, 0x11, 0x2e, 0x27, 0x3c, 0x35, 0x42, 0x4b, 0x50, 0x59, 0x66, 0x6f, 0x74, 0x7d,
+0xa1, 0xa8, 0xb3, 0xba, 0x85, 0x8c, 0x97, 0x9e, 0xe9, 0xe0, 0xfb, 0xf2, 0xcd, 0xc4, 0xdf, 0xd6,
+0x31, 0x38, 0x23, 0x2a, 0x15, 0x1c, 0x07, 0x0e, 0x79, 0x70, 0x6b, 0x62, 0x5d, 0x54, 0x4f, 0x46};
+
+static uchar XtimeB[256] = {
+0x00, 0x0b, 0x16, 0x1d, 0x2c, 0x27, 0x3a, 0x31, 0x58, 0x53, 0x4e, 0x45, 0x74, 0x7f, 0x62, 0x69,
+0xb0, 0xbb, 0xa6, 0xad, 0x9c, 0x97, 0x8a, 0x81, 0xe8, 0xe3, 0xfe, 0xf5, 0xc4, 0xcf, 0xd2, 0xd9,
+0x7b, 0x70, 0x6d, 0x66, 0x57, 0x5c, 0x41, 0x4a, 0x23, 0x28, 0x35, 0x3e, 0x0f, 0x04, 0x19, 0x12,
+0xcb, 0xc0, 0xdd, 0xd6, 0xe7, 0xec, 0xf1, 0xfa, 0x93, 0x98, 0x85, 0x8e, 0xbf, 0xb4, 0xa9, 0xa2,
+0xf6, 0xfd, 0xe0, 0xeb, 0xda, 0xd1, 0xcc, 0xc7, 0xae, 0xa5, 0xb8, 0xb3, 0x82, 0x89, 0x94, 0x9f,
+0x46, 0x4d, 0x50, 0x5b, 0x6a, 0x61, 0x7c, 0x77, 0x1e, 0x15, 0x08, 0x03, 0x32, 0x39, 0x24, 0x2f,
+0x8d, 0x86, 0x9b, 0x90, 0xa1, 0xaa, 0xb7, 0xbc, 0xd5, 0xde, 0xc3, 0xc8, 0xf9, 0xf2, 0xef, 0xe4,
+0x3d, 0x36, 0x2b, 0x20, 0x11, 0x1a, 0x07, 0x0c, 0x65, 0x6e, 0x73, 0x78, 0x49, 0x42, 0x5f, 0x54,
+0xf7, 0xfc, 0xe1, 0xea, 0xdb, 0xd0, 0xcd, 0xc6, 0xaf, 0xa4, 0xb9, 0xb2, 0x83, 0x88, 0x95, 0x9e,
+0x47, 0x4c, 0x51, 0x5a, 0x6b, 0x60, 0x7d, 0x76, 0x1f, 0x14, 0x09, 0x02, 0x33, 0x38, 0x25, 0x2e,
+0x8c, 0x87, 0x9a, 0x91, 0xa0, 0xab, 0xb6, 0xbd, 0xd4, 0xdf, 0xc2, 0xc9, 0xf8, 0xf3, 0xee, 0xe5,
+0x3c, 0x37, 0x2a, 0x21, 0x10, 0x1b, 0x06, 0x0d, 0x64, 0x6f, 0x72, 0x79, 0x48, 0x43, 0x5e, 0x55,
+0x01, 0x0a, 0x17, 0x1c, 0x2d, 0x26, 0x3b, 0x30, 0x59, 0x52, 0x4f, 0x44, 0x75, 0x7e, 0x63, 0x68,
+0xb1, 0xba, 0xa7, 0xac, 0x9d, 0x96, 0x8b, 0x80, 0xe9, 0xe2, 0xff, 0xf4, 0xc5, 0xce, 0xd3, 0xd8,
+0x7a, 0x71, 0x6c, 0x67, 0x56, 0x5d, 0x40, 0x4b, 0x22, 0x29, 0x34, 0x3f, 0x0e, 0x05, 0x18, 0x13,
+0xca, 0xc1, 0xdc, 0xd7, 0xe6, 0xed, 0xf0, 0xfb, 0x92, 0x99, 0x84, 0x8f, 0xbe, 0xb5, 0xa8, 0xa3};
+
+static uchar XtimeD[256] = {
+0x00, 0x0d, 0x1a, 0x17, 0x34, 0x39, 0x2e, 0x23, 0x68, 0x65, 0x72, 0x7f, 0x5c, 0x51, 0x46, 0x4b,
+0xd0, 0xdd, 0xca, 0xc7, 0xe4, 0xe9, 0xfe, 0xf3, 0xb8, 0xb5, 0xa2, 0xaf, 0x8c, 0x81, 0x96, 0x9b,
+0xbb, 0xb6, 0xa1, 0xac, 0x8f, 0x82, 0x95, 0x98, 0xd3, 0xde, 0xc9, 0xc4, 0xe7, 0xea, 0xfd, 0xf0,
+0x6b, 0x66, 0x71, 0x7c, 0x5f, 0x52, 0x45, 0x48, 0x03, 0x0e, 0x19, 0x14, 0x37, 0x3a, 0x2d, 0x20,
+0x6d, 0x60, 0x77, 0x7a, 0x59, 0x54, 0x43, 0x4e, 0x05, 0x08, 0x1f, 0x12, 0x31, 0x3c, 0x2b, 0x26,
+0xbd, 0xb0, 0xa7, 0xaa, 0x89, 0x84, 0x93, 0x9e, 0xd5, 0xd8, 0xcf, 0xc2, 0xe1, 0xec, 0xfb, 0xf6,
+0xd6, 0xdb, 0xcc, 0xc1, 0xe2, 0xef, 0xf8, 0xf5, 0xbe, 0xb3, 0xa4, 0xa9, 0x8a, 0x87, 0x90, 0x9d,
+0x06, 0x0b, 0x1c, 0x11, 0x32, 0x3f, 0x28, 0x25, 0x6e, 0x63, 0x74, 0x79, 0x5a, 0x57, 0x40, 0x4d,
+0xda, 0xd7, 0xc0, 0xcd, 0xee, 0xe3, 0xf4, 0xf9, 0xb2, 0xbf, 0xa8, 0xa5, 0x86, 0x8b, 0x9c, 0x91,
+0x0a, 0x07, 0x10, 0x1d, 0x3e, 0x33, 0x24, 0x29, 0x62, 0x6f, 0x78, 0x75, 0x56, 0x5b, 0x4c, 0x41,
+0x61, 0x6c, 0x7b, 0x76, 0x55, 0x58, 0x4f, 0x42, 0x09, 0x04, 0x13, 0x1e, 0x3d, 0x30, 0x27, 0x2a,
+0xb1, 0xbc, 0xab, 0xa6, 0x85, 0x88, 0x9f, 0x92, 0xd9, 0xd4, 0xc3, 0xce, 0xed, 0xe0, 0xf7, 0xfa,
+0xb7, 0xba, 0xad, 0xa0, 0x83, 0x8e, 0x99, 0x94, 0xdf, 0xd2, 0xc5, 0xc8, 0xeb, 0xe6, 0xf1, 0xfc,
+0x67, 0x6a, 0x7d, 0x70, 0x53, 0x5e, 0x49, 0x44, 0x0f, 0x02, 0x15, 0x18, 0x3b, 0x36, 0x21, 0x2c,
+0x0c, 0x01, 0x16, 0x1b, 0x38, 0x35, 0x22, 0x2f, 0x64, 0x69, 0x7e, 0x73, 0x50, 0x5d, 0x4a, 0x47,
+0xdc, 0xd1, 0xc6, 0xcb, 0xe8, 0xe5, 0xf2, 0xff, 0xb4, 0xb9, 0xae, 0xa3, 0x80, 0x8d, 0x9a, 0x97};
+
+static uchar XtimeE[256] = {
+0x00, 0x0e, 0x1c, 0x12, 0x38, 0x36, 0x24, 0x2a, 0x70, 0x7e, 0x6c, 0x62, 0x48, 0x46, 0x54, 0x5a,
+0xe0, 0xee, 0xfc, 0xf2, 0xd8, 0xd6, 0xc4, 0xca, 0x90, 0x9e, 0x8c, 0x82, 0xa8, 0xa6, 0xb4, 0xba,
+0xdb, 0xd5, 0xc7, 0xc9, 0xe3, 0xed, 0xff, 0xf1, 0xab, 0xa5, 0xb7, 0xb9, 0x93, 0x9d, 0x8f, 0x81,
+0x3b, 0x35, 0x27, 0x29, 0x03, 0x0d, 0x1f, 0x11, 0x4b, 0x45, 0x57, 0x59, 0x73, 0x7d, 0x6f, 0x61,
+0xad, 0xa3, 0xb1, 0xbf, 0x95, 0x9b, 0x89, 0x87, 0xdd, 0xd3, 0xc1, 0xcf, 0xe5, 0xeb, 0xf9, 0xf7,
+0x4d, 0x43, 0x51, 0x5f, 0x75, 0x7b, 0x69, 0x67, 0x3d, 0x33, 0x21, 0x2f, 0x05, 0x0b, 0x19, 0x17,
+0x76, 0x78, 0x6a, 0x64, 0x4e, 0x40, 0x52, 0x5c, 0x06, 0x08, 0x1a, 0x14, 0x3e, 0x30, 0x22, 0x2c,
+0x96, 0x98, 0x8a, 0x84, 0xae, 0xa0, 0xb2, 0xbc, 0xe6, 0xe8, 0xfa, 0xf4, 0xde, 0xd0, 0xc2, 0xcc,
+0x41, 0x4f, 0x5d, 0x53, 0x79, 0x77, 0x65, 0x6b, 0x31, 0x3f, 0x2d, 0x23, 0x09, 0x07, 0x15, 0x1b,
+0xa1, 0xaf, 0xbd, 0xb3, 0x99, 0x97, 0x85, 0x8b, 0xd1, 0xdf, 0xcd, 0xc3, 0xe9, 0xe7, 0xf5, 0xfb,
+0x9a, 0x94, 0x86, 0x88, 0xa2, 0xac, 0xbe, 0xb0, 0xea, 0xe4, 0xf6, 0xf8, 0xd2, 0xdc, 0xce, 0xc0,
+0x7a, 0x74, 0x66, 0x68, 0x42, 0x4c, 0x5e, 0x50, 0x0a, 0x04, 0x16, 0x18, 0x32, 0x3c, 0x2e, 0x20,
+0xec, 0xe2, 0xf0, 0xfe, 0xd4, 0xda, 0xc8, 0xc6, 0x9c, 0x92, 0x80, 0x8e, 0xa4, 0xaa, 0xb8, 0xb6,
+0x0c, 0x02, 0x10, 0x1e, 0x34, 0x3a, 0x28, 0x26, 0x7c, 0x72, 0x60, 0x6e, 0x44, 0x4a, 0x58, 0x56,
+0x37, 0x39, 0x2b, 0x25, 0x0f, 0x01, 0x13, 0x1d, 0x47, 0x49, 0x5b, 0x55, 0x7f, 0x71, 0x63, 0x6d,
+0xd7, 0xd9, 0xcb, 0xc5, 0xef, 0xe1, 0xf3, 0xfd, 0xa7, 0xa9, 0xbb, 0xb5, 0x9f, 0x91, 0x83, 0x8d};
+
+// exchanges columns in each of 4 rows
+// row0 - unchanged, row1- shifted left 1,
+// row2 - shifted left 2 and row3 - shifted left 3
+static void ShiftRows (uchar *state)
+{
+uchar tmp;
+
+ // just substitute row 0
+ state[0] = Sbox[state[0]], state[4] = Sbox[state[4]];
+ state[8] = Sbox[state[8]], state[12] = Sbox[state[12]];
+
+ // rotate row 1
+ tmp = Sbox[state[1]], state[1] = Sbox[state[5]];
+ state[5] = Sbox[state[9]], state[9] = Sbox[state[13]], state[13] = tmp;
+
+ // rotate row 2
+ tmp = Sbox[state[2]], state[2] = Sbox[state[10]], state[10] = tmp;
+ tmp = Sbox[state[6]], state[6] = Sbox[state[14]], state[14] = tmp;
+
+ // rotate row 3
+ tmp = Sbox[state[15]], state[15] = Sbox[state[11]];
+ state[11] = Sbox[state[7]], state[7] = Sbox[state[3]], state[3] = tmp;
+}
+
+// restores columns in each of 4 rows
+// row0 - unchanged, row1- shifted right 1,
+// row2 - shifted right 2 and row3 - shifted right 3
+static void InvShiftRows (uchar *state)
+{
+uchar tmp;
+
+ // restore row 0
+ state[0] = InvSbox[state[0]], state[4] = InvSbox[state[4]];
+ state[8] = InvSbox[state[8]], state[12] = InvSbox[state[12]];
+
+ // restore row 1
+ tmp = InvSbox[state[13]], state[13] = InvSbox[state[9]];
+ state[9] = InvSbox[state[5]], state[5] = InvSbox[state[1]], state[1] = tmp;
+
+ // restore row 2
+ tmp = InvSbox[state[2]], state[2] = InvSbox[state[10]], state[10] = tmp;
+ tmp = InvSbox[state[6]], state[6] = InvSbox[state[14]], state[14] = tmp;
+
+ // restore row 3
+ tmp = InvSbox[state[3]], state[3] = InvSbox[state[7]];
+ state[7] = InvSbox[state[11]], state[11] = InvSbox[state[15]], state[15] = tmp;
+}
+
+// recombine and mix each row in a column
+static void MixSubColumns (uchar *state)
+{
+uchar tmp[4 * Nb];
+
+ // mixing column 0
+ tmp[0] = Xtime2Sbox[state[0]] ^ Xtime3Sbox[state[5]] ^ Sbox[state[10]] ^ Sbox[state[15]];
+ tmp[1] = Sbox[state[0]] ^ Xtime2Sbox[state[5]] ^ Xtime3Sbox[state[10]] ^ Sbox[state[15]];
+ tmp[2] = Sbox[state[0]] ^ Sbox[state[5]] ^ Xtime2Sbox[state[10]] ^ Xtime3Sbox[state[15]];
+ tmp[3] = Xtime3Sbox[state[0]] ^ Sbox[state[5]] ^ Sbox[state[10]] ^ Xtime2Sbox[state[15]];
+
+ // mixing column 1
+ tmp[4] = Xtime2Sbox[state[4]] ^ Xtime3Sbox[state[9]] ^ Sbox[state[14]] ^ Sbox[state[3]];
+ tmp[5] = Sbox[state[4]] ^ Xtime2Sbox[state[9]] ^ Xtime3Sbox[state[14]] ^ Sbox[state[3]];
+ tmp[6] = Sbox[state[4]] ^ Sbox[state[9]] ^ Xtime2Sbox[state[14]] ^ Xtime3Sbox[state[3]];
+ tmp[7] = Xtime3Sbox[state[4]] ^ Sbox[state[9]] ^ Sbox[state[14]] ^ Xtime2Sbox[state[3]];
+
+ // mixing column 2
+ tmp[8] = Xtime2Sbox[state[8]] ^ Xtime3Sbox[state[13]] ^ Sbox[state[2]] ^ Sbox[state[7]];
+ tmp[9] = Sbox[state[8]] ^ Xtime2Sbox[state[13]] ^ Xtime3Sbox[state[2]] ^ Sbox[state[7]];
+ tmp[10] = Sbox[state[8]] ^ Sbox[state[13]] ^ Xtime2Sbox[state[2]] ^ Xtime3Sbox[state[7]];
+ tmp[11] = Xtime3Sbox[state[8]] ^ Sbox[state[13]] ^ Sbox[state[2]] ^ Xtime2Sbox[state[7]];
+
+ // mixing column 3
+ tmp[12] = Xtime2Sbox[state[12]] ^ Xtime3Sbox[state[1]] ^ Sbox[state[6]] ^ Sbox[state[11]];
+ tmp[13] = Sbox[state[12]] ^ Xtime2Sbox[state[1]] ^ Xtime3Sbox[state[6]] ^ Sbox[state[11]];
+ tmp[14] = Sbox[state[12]] ^ Sbox[state[1]] ^ Xtime2Sbox[state[6]] ^ Xtime3Sbox[state[11]];
+ tmp[15] = Xtime3Sbox[state[12]] ^ Sbox[state[1]] ^ Sbox[state[6]] ^ Xtime2Sbox[state[11]];
+
+ memcpy (state, tmp, sizeof(tmp));
+}
+
+// restore and un-mix each row in a column
+static void InvMixSubColumns (uchar *state)
+{
+uchar tmp[4 * Nb];
+int i;
+
+ // restore column 0
+ tmp[0] = XtimeE[state[0]] ^ XtimeB[state[1]] ^ XtimeD[state[2]] ^ Xtime9[state[3]];
+ tmp[5] = Xtime9[state[0]] ^ XtimeE[state[1]] ^ XtimeB[state[2]] ^ XtimeD[state[3]];
+ tmp[10] = XtimeD[state[0]] ^ Xtime9[state[1]] ^ XtimeE[state[2]] ^ XtimeB[state[3]];
+ tmp[15] = XtimeB[state[0]] ^ XtimeD[state[1]] ^ Xtime9[state[2]] ^ XtimeE[state[3]];
+
+ // restore column 1
+ tmp[4] = XtimeE[state[4]] ^ XtimeB[state[5]] ^ XtimeD[state[6]] ^ Xtime9[state[7]];
+ tmp[9] = Xtime9[state[4]] ^ XtimeE[state[5]] ^ XtimeB[state[6]] ^ XtimeD[state[7]];
+ tmp[14] = XtimeD[state[4]] ^ Xtime9[state[5]] ^ XtimeE[state[6]] ^ XtimeB[state[7]];
+ tmp[3] = XtimeB[state[4]] ^ XtimeD[state[5]] ^ Xtime9[state[6]] ^ XtimeE[state[7]];
+
+ // restore column 2
+ tmp[8] = XtimeE[state[8]] ^ XtimeB[state[9]] ^ XtimeD[state[10]] ^ Xtime9[state[11]];
+ tmp[13] = Xtime9[state[8]] ^ XtimeE[state[9]] ^ XtimeB[state[10]] ^ XtimeD[state[11]];
+ tmp[2] = XtimeD[state[8]] ^ Xtime9[state[9]] ^ XtimeE[state[10]] ^ XtimeB[state[11]];
+ tmp[7] = XtimeB[state[8]] ^ XtimeD[state[9]] ^ Xtime9[state[10]] ^ XtimeE[state[11]];
+
+ // restore column 3
+ tmp[12] = XtimeE[state[12]] ^ XtimeB[state[13]] ^ XtimeD[state[14]] ^ Xtime9[state[15]];
+ tmp[1] = Xtime9[state[12]] ^ XtimeE[state[13]] ^ XtimeB[state[14]] ^ XtimeD[state[15]];
+ tmp[6] = XtimeD[state[12]] ^ Xtime9[state[13]] ^ XtimeE[state[14]] ^ XtimeB[state[15]];
+ tmp[11] = XtimeB[state[12]] ^ XtimeD[state[13]] ^ Xtime9[state[14]] ^ XtimeE[state[15]];
+
+ for( i=0; i < 4 * Nb; i++ )
+ state[i] = InvSbox[tmp[i]];
+}
+
+// encrypt/decrypt columns of the key
+// n.b. you can replace this with
+// byte-wise xor if you wish.
+
+static void AddRoundKey (unsigned *state, unsigned *key)
+{
+int idx;
+
+ for( idx = 0; idx < 4; idx++ )
+ state[idx] ^= key[idx];
+}
+
+static uchar Rcon[11] = {
+0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36};
+
+// produce Nb bytes for each round
+void ExpandKey (uchar *key, uchar *expkey)
+{
+uchar tmp0, tmp1, tmp2, tmp3, tmp4;
+unsigned idx;
+
+ memcpy (expkey, key, Nk * 4);
+
+ for( idx = Nk; idx < Nb * (Nr + 1); idx++ ) {
+ tmp0 = expkey[4*idx - 4];
+ tmp1 = expkey[4*idx - 3];
+ tmp2 = expkey[4*idx - 2];
+ tmp3 = expkey[4*idx - 1];
+ if( !(idx % Nk) ) {
+ tmp4 = tmp3;
+ tmp3 = Sbox[tmp0];
+ tmp0 = Sbox[tmp1] ^ Rcon[idx/Nk];
+ tmp1 = Sbox[tmp2];
+ tmp2 = Sbox[tmp4];
+ } else if( Nk > 6 && idx % Nk == 4 ) {
+ tmp0 = Sbox[tmp0];
+ tmp1 = Sbox[tmp1];
+ tmp2 = Sbox[tmp2];
+ tmp3 = Sbox[tmp3];
+ }
+
+ expkey[4*idx+0] = expkey[4*idx - 4*Nk + 0] ^ tmp0;
+ expkey[4*idx+1] = expkey[4*idx - 4*Nk + 1] ^ tmp1;
+ expkey[4*idx+2] = expkey[4*idx - 4*Nk + 2] ^ tmp2;
+ expkey[4*idx+3] = expkey[4*idx - 4*Nk + 3] ^ tmp3;
+ }
+}
+
+// encrypt one 128 bit block
+void Encrypt (uchar *in, uchar *expkey, uchar *out)
+{
+uchar state[Nb * 4];
+unsigned round;
+
+ memcpy (state, in, Nb * 4);
+ AddRoundKey ((unsigned *)state, (unsigned *)expkey);
+
+ for( round = 1; round < Nr + 1; round++ ) {
+ if( round < Nr )
+ MixSubColumns (state);
+ else
+ ShiftRows (state);
+
+ AddRoundKey ((unsigned *)state, (unsigned *)expkey + round * Nb);
+ }
+
+ memcpy (out, state, sizeof(state));
+}
+
+void Decrypt (uchar *in, uchar *expkey, uchar *out)
+{
+uchar state[Nb * 4];
+unsigned round;
+
+ memcpy (state, in, sizeof(state));
+
+ AddRoundKey ((unsigned *)state, (unsigned *)expkey + Nr * Nb);
+ InvShiftRows(state);
+
+ for( round = Nr; round--; )
+ {
+ AddRoundKey ((unsigned *)state, (unsigned *)expkey + round * Nb);
+ if( round )
+ InvMixSubColumns (state);
+ }
+
+ memcpy (out, state, sizeof(state));
+}
diff --git a/libpurple/protocols/mxit/aes.h b/libpurple/protocols/mxit/aes.h
new file mode 100644
index 0000000000..831a30d3c9
--- /dev/null
+++ b/libpurple/protocols/mxit/aes.h
@@ -0,0 +1,39 @@
+// advanced encryption standard
+// author: karl malbrain, malbrain@yahoo.com
+
+/*
+This work, including the source code, documentation
+and related data, is placed into the public domain.
+
+The orginal author is Karl Malbrain.
+
+THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY
+OF ANY KIND, NOT EVEN THE IMPLIED WARRANTY OF
+MERCHANTABILITY. THE AUTHOR OF THIS SOFTWARE,
+ASSUMES _NO_ RESPONSIBILITY FOR ANY CONSEQUENCE
+RESULTING FROM THE USE, MODIFICATION, OR
+REDISTRIBUTION OF THIS SOFTWARE.
+*/
+
+
+#ifndef AES_MALBRAIN
+#define AES_MALBRAIN
+
+
+// AES only supports Nb=4
+#define Nb 4 // number of columns in the state & expanded key
+
+#define Nk 4 // number of columns in a key
+#define Nr 10 // number of rounds in encryption
+
+
+typedef unsigned char uchar;
+
+
+void ExpandKey (uchar *key, uchar *expkey);
+void Encrypt (uchar *in, uchar *expkey, uchar *out);
+void Decrypt (uchar *in, uchar *expkey, uchar *out);
+
+
+#endif /* AES_MALBRAIN */
+
diff --git a/libpurple/protocols/mxit/chunk.c b/libpurple/protocols/mxit/chunk.c
new file mode 100644
index 0000000000..5d0cfe1603
--- /dev/null
+++ b/libpurple/protocols/mxit/chunk.c
@@ -0,0 +1,659 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- handle chunked data (multimedia messages) --
+ *
+ * Pieter Loubser <libpurple@mxit.com>
+ *
+ * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
+ * <http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "purple.h"
+#include "protocol.h"
+#include "mxit.h"
+#include "chunk.h"
+#include "filexfer.h"
+
+
+/*========================================================================================================================
+ * Data-Type encoding
+ */
+
+#if 0
+#include <byteswap.h>
+#if (__BYTE_ORDER == __BIG_ENDIAN)
+#define SWAP_64(x) (x)
+#else
+#define SWAP_64(x) bswap_64(x)
+#endif
+#endif
+
+/*------------------------------------------------------------------------
+ * Encode a single byte in the chunked data.
+ *
+ * @param chunkdata The chunked-data buffer
+ * @param value The byte
+ * @return The number of bytes added.
+ */
+static int add_int8( char* chunkdata, char value )
+{
+ *chunkdata = value;
+
+ return sizeof( char );
+}
+
+/*------------------------------------------------------------------------
+ * Encode a 16-bit value in the chunked data.
+ *
+ * @param chunkdata The chunked-data buffer
+ * @param value The 16-bit value
+ * @return The number of bytes added.
+ */
+static int add_int16( char* chunkdata, short value )
+{
+ value = htons( value ); /* network byte-order */
+ memcpy( chunkdata, &value, sizeof( short ) );
+
+ return sizeof( short );
+}
+
+/*------------------------------------------------------------------------
+ * Encode a 32-bit value in the chunked data.
+ *
+ * @param chunkdata The chunked-data buffer
+ * @param value The 32-bit value
+ * @return The number of bytes added.
+ */
+static int add_int32( char* chunkdata, int value )
+{
+ value = htonl( value ); /* network byte-order */
+ memcpy( chunkdata, &value, sizeof( int ) );
+
+ return sizeof( int );
+}
+
+#if 0
+/*------------------------------------------------------------------------
+ * Encode a 64-bit value in the chunked data.
+ *
+ * @param chunkdata The chunked-data buffer
+ * @param value The 64-bit value
+ * @return The number of bytes added.
+ */
+static int add_int64( char* chunkdata, int64_t value )
+{
+ value = SWAP_64( value ); /* network byte-order */
+ memcpy( chunkdata, &value, sizeof( int64_t ) );
+
+ return sizeof( int64_t );
+}
+#endif
+
+/*------------------------------------------------------------------------
+ * Encode a block of data in the chunked data.
+ *
+ * @param chunkdata The chunked-data buffer
+ * @param data The data to add
+ * @param datalen The length of the data to add
+ * @return The number of bytes added.
+ */
+static int add_data( char* chunkdata, const char* data, int datalen )
+{
+ memcpy( chunkdata, data, datalen );
+
+ return datalen;
+}
+
+/*------------------------------------------------------------------------
+ * Encode a string as UTF-8 in the chunked data.
+ *
+ * @param chunkdata The chunked-data buffer
+ * @param str The string to encode
+ * @return The number of bytes in the string
+ */
+static int add_utf8_string( char* chunkdata, const char* str )
+{
+ int pos = 0;
+ size_t len = strlen( str );
+
+ /* utf8 string length [2 bytes] */
+ pos += add_int16( &chunkdata[pos], len );
+
+ /* utf8 string */
+ pos += add_data( &chunkdata[pos], str, len );
+
+ return pos;
+}
+
+
+/*========================================================================================================================
+ * Data-Type decoding
+ */
+
+/*------------------------------------------------------------------------
+ * Extract a single byte from the chunked data.
+ *
+ * @param chunkdata The chunked-data buffer
+ * @param value The byte
+ * @return The number of bytes extracted.
+ */
+static int get_int8( const char* chunkdata, char* value )
+{
+ *value = *chunkdata;
+
+ return sizeof( char );
+}
+
+/*------------------------------------------------------------------------
+ * Extract a 16-bit value from the chunked data.
+ *
+ * @param chunkdata The chunked-data buffer
+ * @param value The 16-bit value
+ * @return The number of bytes extracted
+ */
+static int get_int16( const char* chunkdata, short* value )
+{
+ *value = ntohs( *( (const short*) chunkdata ) ); /* host byte-order */
+
+ return sizeof( short );
+}
+
+/*------------------------------------------------------------------------
+ * Extract a 32-bit value from the chunked data.
+ *
+ * @param chunkdata The chunked-data buffer
+ * @param value The 32-bit value
+ * @return The number of bytes extracted
+ */
+static int get_int32( const char* chunkdata, int* value )
+{
+ *value = ntohl( *( (const int*) chunkdata ) ); /* host byte-order */
+
+ return sizeof( int );
+}
+
+#if 0
+/*------------------------------------------------------------------------
+ * Extract a 64-bit value from the chunked data.
+ *
+ * @param chunkdata The chunked-data buffer
+ * @param value The 64-bit value
+ * @return The number of bytes extracted
+ */
+static int get_int64( const char* chunkdata, int64_t* value )
+{
+ *value = SWAP_64( *( (const int64_t*) chunkdata ) ); /* host byte-order */
+
+ return sizeof( int64_t );
+}
+#endif
+
+/*------------------------------------------------------------------------
+ * Copy a block of data from the chunked data.
+ *
+ * @param chunkdata The chunked-data buffer
+ * @param dest Where to store the extract data
+ * @param datalen The length of the data to extract
+ * @return The number of bytes extracted
+ */
+static int get_data( const char* chunkdata, char* dest, int datalen )
+{
+ memcpy( dest, chunkdata, datalen );
+
+ return datalen;
+}
+
+/*------------------------------------------------------------------------
+ * Extract a UTF-8 encoded string from the chunked data.
+ *
+ * @param chunkdata The chunked-data buffer
+ * @param str A pointer to extracted string. Must be g_free()'d.
+ * @return The number of bytes consumed
+ */
+static int get_utf8_string( const char* chunkdata, char* str, int maxstrlen )
+{
+ int pos = 0;
+ short len;
+ int skip = 0;
+
+ /* string length [2 bytes] */
+ pos += get_int16( &chunkdata[pos], &len );
+
+ if ( len > maxstrlen ) {
+ /* possible buffer overflow */
+ purple_debug_error( MXIT_PLUGIN_ID, "Buffer overflow detected (get_utf8_string)\n" );
+ skip = len - maxstrlen;
+ len = maxstrlen;
+ }
+
+ /* string data */
+ pos += get_data( &chunkdata[pos], str, len );
+ str[len] = '\0'; /* terminate string */
+
+ return pos + skip;
+}
+
+
+/*========================================================================================================================
+ * Chunked Data encoding
+ */
+
+/*------------------------------------------------------------------------
+ * Encode a "reject file" chunk. (Chunk type 7)
+ *
+ * @param chunkdata Chunked-data buffer
+ * @param fileid A unique ID that identifies this file
+ * @return The number of bytes encoded in the buffer
+ */
+int mxit_chunk_create_reject( char* chunkdata, const char* fileid )
+{
+ int pos = 0;
+
+ /* file id [8 bytes] */
+ pos += add_data( &chunkdata[pos], fileid, MXIT_CHUNK_FILEID_LEN );
+
+ /* rejection reason [1 byte] */
+ pos += add_int8( &chunkdata[pos], REJECT_BY_USER );
+
+ /* rejection description [UTF-8 (optional)] */
+ pos += add_utf8_string( &chunkdata[pos], "" );
+
+ return pos;
+}
+
+
+/*------------------------------------------------------------------------
+ * Encode a "get file" request chunk. (Chunk type 8)
+ *
+ * @param chunkdata Chunked-data buffer
+ * @param fileid A unique ID that identifies this file
+ * @param filesize The number of bytes to retrieve
+ * @param offset The start offset in the file
+ * @return The number of bytes encoded in the buffer
+ */
+int mxit_chunk_create_get( char* chunkdata, const char* fileid, int filesize, int offset )
+{
+ int pos = 0;
+
+ /* file id [8 bytes] */
+ pos += add_data( &chunkdata[pos], fileid, MXIT_CHUNK_FILEID_LEN );
+
+ /* offset [4 bytes] */
+ pos += add_int32( &chunkdata[pos], offset );
+
+ /* length [4 bytes] */
+ pos += add_int32( &chunkdata[pos], filesize );
+
+ return pos;
+}
+
+
+/*------------------------------------------------------------------------
+ * Encode a "received file" chunk. (Chunk type 9)
+ *
+ * @param chunkdata Chunked-data buffer
+ * @param fileid A unique ID that identifies this file
+ * @param status The status of the file transfer (see chunk.h)
+ * @return The number of bytes encoded in the buffer
+ */
+int mxit_chunk_create_received( char* chunkdata, const char* fileid, unsigned char status )
+{
+ int pos = 0;
+
+ /* file id [8 bytes] */
+ pos += add_data( &chunkdata[pos], fileid, MXIT_CHUNK_FILEID_LEN );
+
+ /* status [1 byte] */
+ pos += add_int8( &chunkdata[pos], status );
+
+ return pos;
+}
+
+
+/*------------------------------------------------------------------------
+ * Encode a "send file direct" chunk. (Chunk type 10)
+ *
+ * @param chunkdata Chunked-data buffer
+ * @param username The username of the recipient
+ * @param filename The name of the file being sent
+ * @param data The file contents
+ * @param datalen The size of the file contents
+ * @return The number of bytes encoded in the buffer
+ */
+int mxit_chunk_create_senddirect( char* chunkdata, const char* username, const char* filename, const unsigned char* data, int datalen )
+{
+ int pos = 0;
+ const char* mime = NULL;
+
+ /* data length [4 bytes] */
+ pos += add_int32( &chunkdata[pos], datalen );
+
+ /* number of username(s) [2 bytes] */
+ pos += add_int16( &chunkdata[pos], 1 );
+
+ /* username(s) [UTF-8] */
+ pos += add_utf8_string( &chunkdata[pos], username );
+
+ /* filename [UTF-8] */
+ pos += add_utf8_string( &chunkdata[pos], filename );
+
+ /* file mime type [UTF-8] */
+ mime = file_mime_type( filename, (const char*) data, datalen );
+ pos += add_utf8_string( &chunkdata[pos], mime );
+
+ /* human readable description [UTF-8 (optional)] */
+ pos += add_utf8_string( &chunkdata[pos], "" );
+
+ /* crc [4 bytes] (0 = optional) */
+ pos += add_int32( &chunkdata[pos], 0 );
+
+ /* the actual file data */
+ pos += add_data( &chunkdata[pos], (const char *) data, datalen );
+
+ return pos;
+}
+
+
+/*------------------------------------------------------------------------
+ * Encode a "set avatar" chunk. (Chunk type 13)
+ *
+ * @param chunkdata Chunked-data buffer
+ * @param data The avatar data
+ * @param datalen The size of the avatar data
+ * @return The number of bytes encoded in the buffer
+ */
+int mxit_chunk_create_set_avatar( char* chunkdata, const unsigned char* data, int datalen )
+{
+ const char fileid[MXIT_CHUNK_FILEID_LEN];
+ int pos = 0;
+
+ /* id [8 bytes] */
+ memset( &fileid, 0, sizeof( fileid ) ); /* set to 0 for file upload */
+ pos += add_data( &chunkdata[pos], fileid, MXIT_CHUNK_FILEID_LEN );
+
+ /* size [4 bytes] */
+ pos += add_int32( &chunkdata[pos], datalen );
+
+ /* crc [4 bytes] (0 = optional) */
+ pos += add_int32( &chunkdata[pos], 0 );
+
+ /* the actual file data */
+ pos += add_data( &chunkdata[pos], (const char *) data, datalen );
+
+ return pos;
+}
+
+
+/*------------------------------------------------------------------------
+ * Encode a "get avatar" chunk. (Chunk type 14)
+ *
+ * @param chunkdata Chunked-data buffer
+ * @param mxitId The username who's avatar to download
+ * @param avatarId The Id of the avatar image (as string)
+ * @param imgsize The resolution of the avatar image
+ * @return The number of bytes encoded in the buffer
+ */
+int mxit_chunk_create_get_avatar( char* chunkdata, const char* mxitId, const char* avatarId, unsigned int imgsize )
+{
+ int pos = 0;
+
+ /* number of avatars [4 bytes] */
+ pos += add_int32( &chunkdata[pos], 1 );
+
+ /* username [UTF-8] */
+ pos += add_utf8_string( &chunkdata[pos], mxitId );
+
+ /* avatar id [UTF-8] */
+ pos += add_utf8_string( &chunkdata[pos], avatarId );
+
+ /* avatar format [UTF-8] */
+ pos += add_utf8_string( &chunkdata[pos], MXIT_AVATAR_TYPE );
+
+ /* avatar bit depth [1 byte] */
+ pos += add_int8( &chunkdata[pos], MXIT_AVATAR_BITDEPT );
+
+ /* number of sizes [2 bytes] */
+ pos += add_int16( &chunkdata[pos], 1 );
+
+ /* image size [4 bytes] */
+ pos += add_int32( &chunkdata[pos], imgsize );
+
+ return pos;
+}
+
+
+/*========================================================================================================================
+ * Chunked Data decoding
+ */
+
+/*------------------------------------------------------------------------
+ * Parse a received "offer file" chunk. (Chunk 6)
+ *
+ * @param chunkdata Chunked data buffer
+ * @param datalen The length of the chunked data
+ * @param offer Decoded offerfile information
+ */
+void mxit_chunk_parse_offer( char* chunkdata, int datalen, struct offerfile_chunk* offer )
+{
+ int pos = 0;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_chunk_parse_offer (%i bytes)\n", datalen );
+
+ /* id [8 bytes] */
+ pos += get_data( &chunkdata[pos], offer->fileid, 8);
+
+ /* from username [UTF-8] */
+ pos += get_utf8_string( &chunkdata[pos], offer->username, sizeof( offer->username ) );
+ mxit_strip_domain( offer->username );
+
+ /* file size [4 bytes] */
+ pos += get_int32( &chunkdata[pos], &(offer->filesize) );
+
+ /* filename [UTF-8] */
+ pos += get_utf8_string( &chunkdata[pos], offer->filename, sizeof( offer->filename) );
+
+ /* mime type [UTF-8] */
+ /* not used by libPurple */
+
+ /* timestamp [8 bytes] */
+ /* not used by libPurple */
+
+ /* file description [UTF-8] */
+ /* not used by libPurple */
+
+ /* file alternative [UTF-8] */
+ /* not used by libPurple */
+
+ /* flags [4 bytes] */
+ /* not used by libPurple */
+}
+
+
+/*------------------------------------------------------------------------
+ * Parse a received "get file" response chunk. (Chunk 8)
+ *
+ * @param chunkdata Chunked data buffer
+ * @param datalen The length of the chunked data
+ * @param offer Decoded getfile information
+ */
+void mxit_chunk_parse_get( char* chunkdata, int datalen, struct getfile_chunk* getfile )
+{
+ int pos = 0;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_chunk_parse_file (%i bytes)\n", datalen );
+
+ /* id [8 bytes] */
+ pos += get_data( &chunkdata[pos], getfile->fileid, 8 );
+
+ /* offset [4 bytes] */
+ pos += get_int32( &chunkdata[pos], &(getfile->offset) );
+
+ /* file length [4 bytes] */
+ pos += get_int32( &chunkdata[pos], &(getfile->length) );
+
+ /* crc [4 bytes] */
+ pos += get_int32( &chunkdata[pos], &(getfile->crc) );
+
+ /* file data */
+ getfile->data = &chunkdata[pos];
+}
+
+
+/*------------------------------------------------------------------------
+ * Parse a received splash screen chunk. (Chunk 2)
+ *
+ * @param chunkdata Chunked data buffer
+ * @param datalen The length of the chunked data
+ * @param splash Decoded splash image information
+ */
+static void mxit_chunk_parse_splash( char* chunkdata, int datalen, struct splash_chunk* splash )
+{
+ int pos = 0;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_chunk_parse_splash (%i bytes)\n", datalen );
+
+ /* anchor [1 byte] */
+ pos += get_int8( &chunkdata[pos], &(splash->anchor) );
+
+ /* time to show [1 byte] */
+ pos += get_int8( &chunkdata[pos], &(splash->showtime) );
+
+ /* background color [4 bytes] */
+ pos += get_int32( &chunkdata[pos], &(splash->bgcolor) );
+
+ /* file data */
+ splash->data = &chunkdata[pos];
+
+ /* data length */
+ splash->datalen = datalen - pos;
+}
+
+
+/*------------------------------------------------------------------------
+ * Parse a received "custom resource" chunk. (Chunk 1)
+ *
+ * @param chunkdata Chunked data buffer
+ * @param datalen The length of the chunked data
+ * @param offer Decoded custom resource
+ */
+void mxit_chunk_parse_cr( char* chunkdata, int datalen, struct cr_chunk* cr )
+{
+ int pos = 0;
+ int chunklen = 0;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_chunk_parse_cr (%i bytes)\n", datalen );
+
+ /* id [UTF-8] */
+ pos += get_utf8_string( &chunkdata[pos], cr->id, sizeof( cr->id ) );
+
+ /* handle [UTF-8] */
+ pos += get_utf8_string( &chunkdata[pos], cr->handle, sizeof( cr->handle ) );
+
+ /* operation [1 byte] */
+ pos += get_int8( &chunkdata[pos], &(cr->operation) );
+
+ /* chunk size [4 bytes] */
+ pos += get_int32( &chunkdata[pos], &chunklen );
+
+ /* parse the resource chunks */
+ while ( chunklen > 0 ) {
+ struct raw_chunk* chunkhdr = ( struct raw_chunk * ) &chunkdata[pos];
+ chunkhdr->length = ntohl( chunkhdr->length ); /* host byte-order */
+
+ /* start of chunk data */
+ pos += sizeof( struct raw_chunk );
+
+ switch ( chunkhdr->type ) {
+ case CP_CHUNK_SPLASH : /* splash image */
+ {
+ struct splash_chunk* splash = g_new0( struct splash_chunk, 1 );
+
+ mxit_chunk_parse_splash( &chunkdata[pos], chunkhdr->length, splash );
+
+ cr->resources = g_list_append( cr->resources, splash );
+ break;
+ }
+ case CP_CHUNK_CLICK : /* splash click */
+ {
+ struct splash_click_chunk* click = g_new0( struct splash_click_chunk, 1 );
+
+ cr->resources = g_list_append( cr->resources, click );
+ break;
+ }
+ default:
+ purple_debug_info( MXIT_PLUGIN_ID, "Unsupported custom resource chunk received (%i)\n", chunkhdr->type );
+ }
+
+ /* skip over data to next resource chunk */
+ pos += chunkhdr->length;
+ chunklen -= ( sizeof( struct raw_chunk ) + chunkhdr->length );
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Parse a received "get avatar" response chunk. (Chunk 14)
+ *
+ * @param chunkdata Chunked data buffer
+ * @param datalen The length of the chunked data
+ * @param avatar Decoded avatar information
+ */
+void mxit_chunk_parse_get_avatar( char* chunkdata, int datalen, struct getavatar_chunk* avatar )
+{
+ int pos = 0;
+ int numfiles = 0;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_chunk_parse_get_avatar (%i bytes)\n", datalen );
+
+ /* number of files [4 bytes] */
+ pos += get_int32( &chunkdata[pos], &numfiles );
+
+ if ( numfiles < 1 ) /* no data */
+ return;
+
+ /* mxitId [UTF-8 string] */
+ pos += get_utf8_string( &chunkdata[pos], avatar->mxitid, sizeof( avatar->mxitid ) );
+
+ /* avatar id [UTF-8 string] */
+ pos += get_utf8_string( &chunkdata[pos], avatar->avatarid, sizeof( avatar->avatarid ) );
+
+ /* format [UTF-8 string] */
+ pos += get_utf8_string( &chunkdata[pos], avatar->format, sizeof( avatar->format ) );
+
+ /* bit depth [1 byte] */
+ pos += get_int8( &chunkdata[pos], &(avatar->bitdepth) );
+
+ /* crc [4 bytes] */
+ pos += get_int32( &chunkdata[pos], &(avatar->crc) );
+
+ /* width [4 bytes] */
+ pos += get_int32( &chunkdata[pos], &(avatar->width) );
+
+ /* height [4 bytes] */
+ pos += get_int32( &chunkdata[pos], &(avatar->height) );
+
+ /* file length [4 bytes] */
+ pos += get_int32( &chunkdata[pos], &(avatar->length) );
+
+ /* file data */
+ avatar->data = &chunkdata[pos];
+}
diff --git a/libpurple/protocols/mxit/chunk.h b/libpurple/protocols/mxit/chunk.h
new file mode 100644
index 0000000000..b4247db66e
--- /dev/null
+++ b/libpurple/protocols/mxit/chunk.h
@@ -0,0 +1,140 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- handle chunked data (multimedia messages) --
+ *
+ * Pieter Loubser <libpurple@mxit.com>
+ *
+ * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
+ * <http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#ifndef _MXIT_CHUNK_H_
+#define _MXIT_CHUNK_H_
+
+
+#include "roster.h"
+
+
+#define MXIT_CHUNK_FILEID_LEN 8 /* bytes */
+
+/* Multimedia chunk types */
+#define CP_CHUNK_NONE 0x00 /* (0) no chunk */
+#define CP_CHUNK_CUSTOM 0x01 /* (1) custom resource */
+#define CP_CHUNK_SPLASH 0x02 /* (2) splash image */
+#define CP_CHUNK_CLICK 0x03 /* (3) splash click through */
+#define CP_CHUNK_OFFER 0x06 /* (6) offer file */
+#define CP_CHUNK_REJECT 0x07 /* (7) reject file */
+#define CP_CHUNK_GET 0x08 /* (8) get file */
+#define CP_CHUNK_RECIEVED 0x09 /* (9) received file */
+#define CP_CHUNK_DIRECT_SND 0x0A /* (10) send file direct */
+#define CP_CHUNK_DIRECT_FWD 0x0B /* (11) forward file direct */
+#define CP_CHUNK_SKIN 0x0C /* (12) MXit client skin */
+#define CP_CHUNK_SET_AVATAR 0x0D /* (13) set avatar */
+#define CP_CHUNK_GET_AVATAR 0x0E /* (14) get avatar */
+#define CP_CHUNK_END 0x7E /* (126) end */
+#define CP_CHUNK_EXT 0x7F /* (127) extended type */
+
+
+/* Custom Resource operations */
+#define CR_OP_UPDATE 0
+#define CR_OP_REMOVE 1
+
+/* File Received status */
+#define RECV_STATUS_SUCCESS 0
+#define RECV_STATUS_PARSE_FAIL 1
+#define RECV_STATUS_CANNOT_OPEN 8
+#define RECV_STATUS_BAD_CRC 9
+#define RECV_STATUS_BAD_ID 10
+
+/* File Reject status */
+#define REJECT_BY_USER 1
+#define REJECT_FILETYPE 2
+#define REJECT_NO_RESOURCES 3
+#define REJECT_BAD_RECIPIENT 4
+
+/*
+ * a Chunk header
+ */
+struct raw_chunk {
+ guint8 type;
+ guint32 length;
+ gchar data[0];
+} __attribute__ ((packed));
+
+struct offerfile_chunk {
+ char fileid[MXIT_CHUNK_FILEID_LEN];
+ char username[MXIT_CP_MAX_JID_LEN + 1];
+ int filesize;
+ char filename[FILENAME_MAX];
+};
+
+struct getfile_chunk {
+ char fileid[MXIT_CHUNK_FILEID_LEN];
+ int offset;
+ int length;
+ int crc;
+ char* data;
+};
+
+struct cr_chunk {
+ char id[64];
+ char handle[64];
+ char operation;
+ GList* resources;
+};
+
+struct splash_chunk {
+ char anchor;
+ char showtime;
+ int bgcolor;
+ char* data;
+ int datalen;
+};
+
+struct splash_click_chunk {
+ char reserved[1];
+};
+
+struct getavatar_chunk {
+ char mxitid[50];
+ char avatarid[64];
+ char format[16];
+ char bitdepth;
+ int crc;
+ int width;
+ int height;
+ int length;
+ char* data;
+};
+
+/* Encode chunk */
+int mxit_chunk_create_senddirect( char* chunkdata, const char* username, const char* filename, const unsigned char* data, int datalen );
+int mxit_chunk_create_reject( char* chunkdata, const char* fileid );
+int mxit_chunk_create_get( char* chunkdata, const char* fileid, int filesize, int offset );
+int mxit_chunk_create_received( char* chunkdata, const char* fileid, unsigned char status );
+int mxit_chunk_create_set_avatar( char* chunkdata, const unsigned char* data, int datalen );
+int mxit_chunk_create_get_avatar( char* chunkdata, const char* mxitId, const char* avatarId, unsigned int imgsize );
+
+/* Decode chunk */
+void mxit_chunk_parse_offer( char* chunkdata, int datalen, struct offerfile_chunk* offer );
+void mxit_chunk_parse_get( char* chunkdata, int datalen, struct getfile_chunk* getfile );
+void mxit_chunk_parse_cr( char* chunkdata, int datalen, struct cr_chunk* cr );
+void mxit_chunk_parse_get_avatar( char* chunkdata, int datalen, struct getavatar_chunk* avatar );
+
+#endif /* _MXIT_CHUNK_H_ */
+
diff --git a/libpurple/protocols/mxit/cipher.c b/libpurple/protocols/mxit/cipher.c
new file mode 100644
index 0000000000..46d4b6a337
--- /dev/null
+++ b/libpurple/protocols/mxit/cipher.c
@@ -0,0 +1,111 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- user password encryption --
+ *
+ * Pieter Loubser <libpurple@mxit.com>
+ *
+ * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
+ * <http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "purple.h"
+
+#include "mxit.h"
+#include "cipher.h"
+#include "aes.h"
+
+
+/* password encryption */
+#define INITIAL_KEY "6170383452343567"
+#define SECRET_HEADER "<mxit/>"
+
+
+/*------------------------------------------------------------------------
+ * Pad the secret data using ISO10126 Padding.
+ *
+ * @param secret The data to pad (caller must ensure buffer has enough space for padding)
+ * @return The total number of 128-bit blocks used
+ */
+static int pad_secret_data( char* secret )
+{
+ int blocks = 0;
+ int passlen;
+ int padding;
+
+ passlen = strlen( secret );
+ blocks = ( passlen / 16 ) + 1;
+ padding = ( blocks * 16 ) - passlen;
+ secret[passlen] = 0x50;
+ secret[(blocks * 16) - 1] = padding;
+
+ return blocks;
+}
+
+
+/*------------------------------------------------------------------------
+ * Encrypt the user's cleartext password using the AES 128-bit (ECB)
+ * encryption algorithm.
+ *
+ * @param session The MXit session object
+ * @return The encrypted & encoded password. Must be g_free'd when no longer needed.
+ */
+char* mxit_encrypt_password( struct MXitSession* session )
+{
+ char key[64];
+ char exkey[512];
+ char pass[64];
+ char encrypted[64];
+ char* base64;
+ int blocks;
+ int size;
+ int i;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_encrypt_password\n" );
+
+ memset( encrypted, 0x00, sizeof( encrypted ) );
+ memset( exkey, 0x00, sizeof( exkey ) );
+ memset( pass, 0x58, sizeof( pass ) );
+ pass[sizeof( pass ) - 1] = '\0';
+
+ /* build the custom AES encryption key */
+ strcpy( key, INITIAL_KEY );
+ memcpy( key, session->clientkey, strlen( session->clientkey ) );
+ ExpandKey( (unsigned char*) key, (unsigned char*) exkey );
+
+ /* build the custom data to be encrypted */
+ strcpy( pass, SECRET_HEADER );
+ strcat( pass, session->acc->password );
+
+ /* pad the secret data */
+ blocks = pad_secret_data( pass );
+ size = blocks * 16;
+
+ /* now encrypt the password. we encrypt each block separately (ECB mode) */
+ for ( i = 0; i < size; i += 16 )
+ Encrypt( (unsigned char*) pass + i, (unsigned char*) exkey, (unsigned char*) encrypted + i );
+
+ /* now base64 encode the encrypted password */
+ base64 = purple_base64_encode( (unsigned char*) encrypted, size );
+
+ return base64;
+}
+
diff --git a/libpurple/protocols/mxit/cipher.h b/libpurple/protocols/mxit/cipher.h
new file mode 100644
index 0000000000..188a0609b1
--- /dev/null
+++ b/libpurple/protocols/mxit/cipher.h
@@ -0,0 +1,36 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- user password encryption --
+ *
+ * Pieter Loubser <libpurple@mxit.com>
+ *
+ * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
+ * <http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#ifndef _MXIT_CIPHER_H_
+#define _MXIT_CIPHER_H_
+
+
+struct MXitSession;
+
+
+char* mxit_encrypt_password( struct MXitSession* session );
+
+
+#endif /* _MXIT_CIPHER_H_ */
diff --git a/libpurple/protocols/mxit/filexfer.c b/libpurple/protocols/mxit/filexfer.c
new file mode 100644
index 0000000000..a9254ff7fd
--- /dev/null
+++ b/libpurple/protocols/mxit/filexfer.c
@@ -0,0 +1,454 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- file transfers (sending and receiving) --
+ *
+ * Pieter Loubser <libpurple@mxit.com>
+ *
+ * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
+ * <http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "purple.h"
+#include "protocol.h"
+#include "mxit.h"
+#include "chunk.h"
+#include "filexfer.h"
+
+
+#define MIME_TYPE_OCTETSTREAM "application/octet-stream"
+
+
+/* supported file mime types */
+static struct mime_type {
+ const char* magic;
+ const short magic_len;
+ const char* mime;
+} const mime_types[] = {
+ /* magic length mime */
+ /* images */ { "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", 8, "image/png" }, /* image png */
+ { "\xFF\xD8", 2, "image/jpeg" }, /* image jpeg */
+ { "\x3C\x3F\x78\x6D\x6C", 5, "image/svg+xml" }, /* image SVGansi */
+ { "\xEF\xBB\xBF", 3, "image/svg+xml" }, /* image SVGutf */
+ { "\xEF\xBB\xBF", 3, "image/svg+xml" }, /* image SVGZ */
+ /* mxit */ { "\x4d\x58\x4d", 3, "application/mxit-msgs" }, /* mxit message */
+ { "\x4d\x58\x44\x01", 4, "application/mxit-mood" }, /* mxit mood */
+ { "\x4d\x58\x45\x01", 4, "application/mxit-emo" }, /* mxit emoticon */
+ { "\x4d\x58\x46\x01", 4, "application/mxit-emof" }, /* mxit emoticon frame */
+ { "\x4d\x58\x53\x01", 4, "application/mxit-skin" }, /* mxit skin */
+ /* audio */ { "\x4d\x54\x68\x64", 4, "audio/midi" }, /* audio midi */
+ { "\x52\x49\x46\x46", 4, "audio/wav" }, /* audio wav */
+ { "\xFF\xF1", 2, "audio/aac" }, /* audio aac1 */
+ { "\xFF\xF9", 2, "audio/aac" }, /* audio aac2 */
+ { "\xFF", 1, "audio/mp3" }, /* audio mp3 */
+ { "\x23\x21\x41\x4D\x52\x0A", 6, "audio/amr" }, /* audio AMR */
+ { "\x23\x21\x41\x4D\x52\x2D\x57\x42", 8, "audio/amr-wb" }, /* audio AMR WB */
+ { "\x00\x00\x00", 3, "audio/mp4" }, /* audio mp4 */
+ { "\x2E\x73\x6E\x64", 4, "audio/au" } /* audio AU */
+};
+
+
+/*------------------------------------------------------------------------
+ * Return the MIME type matching the data file.
+ *
+ * @param filename The name of file
+ * @param buf The data
+ * @param buflen The length of the data
+ * @return A MIME type string
+ */
+const char* file_mime_type( const char* filename, const char* buf, int buflen )
+{
+ unsigned int i;
+
+ /* check for matching magic headers */
+ for ( i = 0; i < ARRAY_SIZE( mime_types ); i++ ) {
+
+ if ( buflen < mime_types[i].magic_len ) /* data is shorter than size of magic */
+ continue;
+
+ if ( memcmp( buf, mime_types[i].magic, mime_types[i].magic_len ) == 0 )
+ return mime_types[i].mime;
+ }
+
+ /* we did not find the MIME type, so return the default (application/octet-stream) */
+ return MIME_TYPE_OCTETSTREAM;
+}
+
+
+/*------------------------------------------------------------------------
+ * Cleanup and deallocate a MXit file transfer object
+ *
+ * @param xfer The file transfer object
+ */
+static void mxit_xfer_free( PurpleXfer* xfer )
+{
+ struct mxitxfer* mx = (struct mxitxfer*) xfer->data;;
+
+ if ( mx ) {
+ g_free( mx );
+ xfer->data = NULL;
+ }
+}
+
+
+/*========================================================================================================================
+ * File Transfer callbacks
+ */
+
+/*------------------------------------------------------------------------
+ * Initialise a new file transfer.
+ *
+ * @param xfer The file transfer object
+ */
+static void mxit_xfer_init( PurpleXfer* xfer )
+{
+ struct mxitxfer* mx = (struct mxitxfer*) xfer->data;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_init\n" );
+
+ if ( purple_xfer_get_type( xfer ) == PURPLE_XFER_SEND ) {
+ /* we are trying to send a file to MXit */
+
+ if ( purple_xfer_get_size( xfer ) > CP_MAX_FILESIZE ) {
+ /* the file is too big */
+ purple_xfer_error( xfer->type, xfer->account, xfer->who, _( "The file you are trying to send is too large!" ) );
+ purple_xfer_cancel_local( xfer );
+ return;
+ }
+
+ /* start the file transfer */
+ purple_xfer_start( xfer, -1, NULL, 0 );
+ }
+ else {
+ /*
+ * we have just accepted a file transfer request from MXit. send a confirmation
+ * to the MXit server so that can send us the file
+ */
+ mxit_send_file_accept( mx->session, mx->fileid, purple_xfer_get_size( xfer ), 0 );
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Start the file transfer.
+ *
+ * @param xfer The file transfer object
+ */
+static void mxit_xfer_start( PurpleXfer* xfer )
+{
+ unsigned char* buffer;
+ int size;
+ int wrote;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_start\n" );
+
+ if ( purple_xfer_get_type( xfer ) == PURPLE_XFER_SEND ) {
+ /*
+ * the user wants to send a file to one of his contacts. we need to create
+ * a buffer and copy the file data into memory and then we can send it to
+ * the contact. we will send the whole file with one go.
+ */
+ buffer = g_malloc( xfer->bytes_remaining );
+ size = fread( buffer, xfer->bytes_remaining, 1, xfer->dest_fp );
+
+ wrote = purple_xfer_write( xfer, buffer, xfer->bytes_remaining );
+ if ( wrote > 0 )
+ purple_xfer_set_bytes_sent( xfer, wrote );
+
+ /* free the buffer */
+ g_free( buffer );
+ buffer = NULL;
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * The file transfer has ended.
+ *
+ * @param xfer The file transfer object
+ */
+static void mxit_xfer_end( PurpleXfer* xfer )
+{
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_end\n" );
+
+ /* deallocate object */
+ mxit_xfer_free( xfer );
+}
+
+
+/*------------------------------------------------------------------------
+ * The file transfer (to a user) has been cancelled.
+ *
+ * @param xfer The file transfer object
+ */
+static void mxit_xfer_cancel_send( PurpleXfer* xfer )
+{
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_cancel_send\n" );
+
+ /* deallocate object */
+ mxit_xfer_free( xfer );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send the file data.
+ *
+ * @param buffer The data to sent
+ * @param size The length of the data to send
+ * @param xfer The file transfer object
+ * @return The amount of data actually sent
+ */
+static gssize mxit_xfer_write( const guchar* buffer, size_t size, PurpleXfer* xfer )
+{
+ struct mxitxfer* mx = (struct mxitxfer*) xfer->data;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_write\n" );
+
+ if ( !mx ) {
+ purple_debug_warning( MXIT_PLUGIN_ID, "mxit_xfer_write: invalid internal mxit xfer data\n" );
+ return -1;
+ }
+ else if ( purple_xfer_get_type( xfer ) != PURPLE_XFER_SEND ) {
+ purple_debug_warning( MXIT_PLUGIN_ID, "mxit_xfer_write: wrong xfer type received\n" );
+ return -1;
+ }
+
+ /* create and send the packet to MXit */
+ mxit_send_file( mx->session, purple_xfer_get_remote_user( xfer ), purple_xfer_get_filename( xfer ), buffer, size );
+
+ /* the transfer is complete */
+ purple_xfer_set_completed( xfer, TRUE );
+
+ return size;
+}
+
+
+/*------------------------------------------------------------------------
+ * The user has rejected a file offer from MXit.
+ *
+ * @param xfer The file transfer object
+ */
+static void mxit_xfer_request_denied( PurpleXfer* xfer )
+{
+ struct mxitxfer* mx = (struct mxitxfer*) xfer->data;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_request_denied\n" );
+
+ /* send file reject packet to MXit server */
+ mxit_send_file_reject( mx->session, mx->fileid );
+
+ /* deallocate object */
+ mxit_xfer_free( xfer );
+}
+
+
+/*------------------------------------------------------------------------
+ * The file transfer (from MXit) has been cancelled.
+ */
+static void mxit_xfer_cancel_recv( PurpleXfer* xfer )
+{
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_cancel_recv\n" );
+
+ /* deallocate object */
+ mxit_xfer_free( xfer );
+}
+
+
+/*========================================================================================================================
+ * Callbacks from libPurple
+ */
+
+/*------------------------------------------------------------------------
+ * Indicate if file transfers are supported to this contact.
+ * For MXit file transfers are always supported.
+ *
+ * @param gc The connection object
+ * @param who The username of the contact
+ * @return TRUE if file transfers are supported
+ */
+gboolean mxit_xfer_enabled( PurpleConnection* gc, const char* who )
+{
+ return TRUE;
+}
+
+
+/*------------------------------------------------------------------------
+ * Create and initialize a new file transfer to a contact.
+ *
+ * @param gc The connection object
+ * @param who The username of the recipient
+ */
+PurpleXfer* mxit_xfer_new( PurpleConnection* gc, const char* who )
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+ PurpleXfer* xfer = NULL;
+ struct mxitxfer* mx = NULL;
+
+ /* (reference: "libpurple/ft.h") */
+ xfer = purple_xfer_new( session->acc, PURPLE_XFER_SEND, who );
+
+ /* create file info and attach it to the file transfer */
+ mx = g_new0( struct mxitxfer, 1 );
+ mx->session = session;
+ xfer->data = mx;
+
+ /* configure callbacks (reference: "libpurple/ft.h") */
+ purple_xfer_set_init_fnc( xfer, mxit_xfer_init );
+ purple_xfer_set_start_fnc( xfer, mxit_xfer_start );
+ purple_xfer_set_end_fnc( xfer, mxit_xfer_end );
+ purple_xfer_set_cancel_send_fnc( xfer, mxit_xfer_cancel_send );
+ purple_xfer_set_write_fnc( xfer, mxit_xfer_write );
+
+ return xfer;
+}
+
+
+/*------------------------------------------------------------------------
+ * The user has initiated a file transfer to a contact.
+ *
+ * @param gc The connection object
+ * @param who The username of the contact
+ * @param filename The filename (is NULL if request has not been accepted yet)
+ */
+void mxit_xfer_tx( PurpleConnection* gc, const char* who, const char* filename )
+{
+ PurpleXfer *xfer = mxit_xfer_new( gc, who );
+
+ if ( filename )
+ purple_xfer_request_accepted( xfer, filename );
+ else
+ purple_xfer_request( xfer );
+}
+
+
+/*========================================================================================================================
+ * Calls from the MXit Protocol layer
+ */
+
+/*------------------------------------------------------------------------
+ * A file transfer offer has been received from the MXit server.
+ *
+ * @param session The MXit session object
+ * @param usermame The username of the sender
+ * @param filename The name of the file being offered
+ * @param filesize The size of the file being offered
+ * @param fileid A unique ID that identifies this file
+ */
+void mxit_xfer_rx_offer( struct MXitSession* session, const char* username, const char* filename, int filesize, const char* fileid )
+{
+ PurpleXfer* xfer = NULL;
+ struct mxitxfer* mx = NULL;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "File Offer: file=%s, from=%s, size=%i\n", filename, username, filesize );
+
+ xfer = purple_xfer_new( session->acc, PURPLE_XFER_RECEIVE, username );
+ if ( xfer ) {
+ /* create a new mxit xfer struct for internal use */
+ mx = g_new0( struct mxitxfer, 1 );
+ mx->session = session;
+ memcpy( mx->fileid, fileid, MXIT_CHUNK_FILEID_LEN );
+ xfer->data = mx;
+
+ purple_xfer_set_filename( xfer, filename );
+ if( filesize > 0 )
+ purple_xfer_set_size( xfer, filesize );
+
+ /* register file transfer callback functions */
+ purple_xfer_set_init_fnc( xfer, mxit_xfer_init );
+ purple_xfer_set_request_denied_fnc( xfer, mxit_xfer_request_denied );
+ purple_xfer_set_cancel_recv_fnc( xfer, mxit_xfer_cancel_recv );
+ purple_xfer_set_end_fnc( xfer, mxit_xfer_end );
+
+ /* give the request to the user to accept/deny */
+ purple_xfer_request( xfer );
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Return the libPurple file-transfer object associated with a MXit transfer
+ *
+ * @param session The MXit session object
+ * @param fileid A unique ID that identifies this file
+ */
+static PurpleXfer* find_mxit_xfer( struct MXitSession* session, const char* fileid )
+{
+ GList* item = NULL;
+ PurpleXfer* xfer = NULL;
+
+ item = purple_xfers_get_all(); /* list of all active transfers */
+ while ( item ) {
+ xfer = item->data;
+
+ if ( xfer->account == session->acc ) {
+ /* transfer is associated with this MXit account */
+ struct mxitxfer* mx = xfer->data;
+
+ /* does the fileid match? */
+ if ( ( mx ) && ( memcmp( mx->fileid, fileid, MXIT_CHUNK_FILEID_LEN ) == 0 ) )
+ break;
+ }
+
+ item = g_list_next( item );
+ }
+
+ if ( item )
+ return item->data;
+ else
+ return NULL;
+}
+
+/*------------------------------------------------------------------------
+ * A file has been received from the MXit server.
+ *
+ * @param session The MXit session object
+ * @param fileid A unique ID that identifies this file
+ * @param data The file data
+ * @param datalen The size of the data
+ */
+void mxit_xfer_rx_file( struct MXitSession* session, const char* fileid, const char* data, int datalen )
+{
+ PurpleXfer* xfer = NULL;
+ struct mxitxfer* mx = NULL;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_rx_file: (size=%i)\n", datalen );
+
+ /* find the file-transfer object */
+ xfer = find_mxit_xfer( session, fileid );
+ if ( xfer ) {
+ mx = xfer->data;
+
+ /* this is the transfer we have been looking for */
+ purple_xfer_ref( xfer );
+ purple_xfer_start( xfer, -1, NULL, 0 );
+ fwrite( data, datalen, 1, xfer->dest_fp );
+ purple_xfer_unref( xfer );
+ purple_xfer_set_completed( xfer, TRUE );
+ purple_xfer_end( xfer );
+
+ /* inform MXit that file was successfully received */
+ mxit_send_file_received( session, fileid, RECV_STATUS_SUCCESS );
+ }
+ else {
+ /* file transfer not found */
+ mxit_send_file_received( session, fileid, RECV_STATUS_BAD_ID );
+ }
+}
diff --git a/libpurple/protocols/mxit/filexfer.h b/libpurple/protocols/mxit/filexfer.h
new file mode 100644
index 0000000000..4ec953d928
--- /dev/null
+++ b/libpurple/protocols/mxit/filexfer.h
@@ -0,0 +1,50 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- file transfers (sending and receiving) --
+ *
+ * Pieter Loubser <libpurple@mxit.com>
+ *
+ * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
+ * <http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#ifndef _MXIT_FILEXFER_H_
+#define _MXIT_FILEXFER_H_
+
+
+/*
+ * a MXit file transfer
+ */
+struct mxitxfer {
+ struct MXitSession* session;
+ char fileid[MXIT_CHUNK_FILEID_LEN];
+};
+
+const char* file_mime_type( const char* filename, const char* buf, int buflen );
+
+/* libPurple callbacks */
+gboolean mxit_xfer_enabled( PurpleConnection* gc, const char* who );
+void mxit_xfer_tx( PurpleConnection* gc, const char* who, const char* filename );
+PurpleXfer* mxit_xfer_new( PurpleConnection* gc, const char* who );
+
+/* MXit Protocol callbacks */
+void mxit_xfer_rx_offer( struct MXitSession* session, const char* username, const char* filename, int filesize, const char* fileid );
+void mxit_xfer_rx_file( struct MXitSession* session, const char* fileid, const char* data, int datalen );
+
+
+#endif /* _MXIT_FILEXFER_H_ */
diff --git a/libpurple/protocols/mxit/formcmds.c b/libpurple/protocols/mxit/formcmds.c
new file mode 100644
index 0000000000..cdb8258502
--- /dev/null
+++ b/libpurple/protocols/mxit/formcmds.c
@@ -0,0 +1,397 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- MXit Forms & Commands --
+ *
+ * Andrew Victor <libpurple@mxit.com>
+ *
+ * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
+ * <http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include <string.h>
+#include <glib.h>
+#include <glib/gprintf.h>
+
+#include "purple.h"
+
+#include "protocol.h"
+#include "mxit.h"
+#include "markup.h"
+#include "formcmds.h"
+
+#undef MXIT_DEBUG_COMMANDS
+
+/*
+ * the MXit Command identifiers
+ */
+typedef enum
+{
+ MXIT_CMD_UNKNOWN = 0, /* Unknown command */
+ MXIT_CMD_CLRSCR, /* Clear screen (clrmsgscreen) */
+ MXIT_CMD_SENDSMS, /* Send SMS (sendsms) */
+ MXIT_CMD_REPLY, /* Reply (reply) */
+ MXIT_CMD_PLATREQ, /* Platform Request (platreq) */
+ MXIT_CMD_SELECTCONTACT, /* Select Contact (selc) */
+ MXIT_CMD_IMAGE /* Inline image (img) */
+} MXitCommandType;
+
+
+/*
+ * object for an inline image request with an URL
+ */
+struct ii_url_request
+{
+ struct RXMsgData* mx;
+ char* url;
+};
+
+
+/*------------------------------------------------------------------------
+ * Callback function invoked when an inline image request to a web site completes.
+ *
+ * @param url_data
+ * @param user_data The Markup message object
+ * @param url_text The data returned from the WAP site
+ * @param len The length of the data returned
+ * @param error_message Descriptive error message
+ */
+static void mxit_cb_ii_returned(PurpleUtilFetchUrlData* url_data, gpointer user_data, const gchar* url_text, gsize len, const gchar* error_message)
+{
+ struct ii_url_request* iireq = (struct ii_url_request*) user_data;
+ char* ii_data;
+ int* intptr = NULL;
+ int id;
+
+#ifdef MXIT_DEBUG_COMMANDS
+ purple_debug_info(MXIT_PLUGIN_ID, "Inline Image returned from %s\n", iireq->url);
+#endif
+
+ if (!url_text) {
+ /* no reply from the WAP site */
+ purple_debug_error(MXIT_PLUGIN_ID, "Error downloading Inline Image from %s.\n", iireq->url);
+ goto done;
+ }
+
+ /* lets first see if we dont have the inline image already in cache */
+ if (g_hash_table_lookup(iireq->mx->session->iimages, iireq->url)) {
+ /* inline image found in the cache, so we just ignore this reply */
+ goto done;
+ }
+
+ /* make a copy of the data */
+ ii_data = g_malloc(len);
+ memcpy(ii_data, (const char*) url_text, len);
+
+ /* we now have the inline image, store it in the imagestore */
+ id = purple_imgstore_add_with_id(ii_data, len, NULL);
+
+ /* map the inline image id to purple image id */
+ intptr = g_malloc(sizeof(int));
+ *intptr = id;
+ g_hash_table_insert(iireq->mx->session->iimages, iireq->url, intptr);
+
+ iireq->mx->flags |= PURPLE_MESSAGE_IMAGES;
+
+done:
+ iireq->mx->img_count--;
+ if ((iireq->mx->img_count == 0) && (iireq->mx->converted)) {
+ /*
+ * this was the last outstanding emoticon for this message,
+ * so we can now display it to the user.
+ */
+ mxit_show_message(iireq->mx);
+ }
+
+ g_free(iireq);
+}
+
+
+/*------------------------------------------------------------------------
+ * Return the command identifier of this MXit Command.
+ *
+ * @param cmd The MXit command <key,value> map
+ * @return The MXit command identifier
+ */
+static MXitCommandType command_type(GHashTable* hash)
+{
+ char* op;
+ char* type;
+
+ op = g_hash_table_lookup(hash, "op");
+ if (op) {
+ if ( strcmp(op, "cmd") == 0 ) {
+ type = g_hash_table_lookup(hash, "type");
+ if (type == NULL) /* no command provided */
+ return MXIT_CMD_UNKNOWN;
+ else if (strcmp(type, "clrmsgscreen") == 0) /* clear the screen */
+ return MXIT_CMD_CLRSCR;
+ else if (strcmp(type, "sendsms") == 0) /* send an SMS */
+ return MXIT_CMD_SENDSMS;
+ else if (strcmp(type, "reply") == 0) /* list of options */
+ return MXIT_CMD_REPLY;
+ else if (strcmp(type, "platreq") == 0) /* platform request */
+ return MXIT_CMD_PLATREQ;
+ else if (strcmp(type, "selc") == 0) /* select contact */
+ return MXIT_CMD_SELECTCONTACT;
+ }
+ else if (strcmp(op, "img") == 0)
+ return MXIT_CMD_IMAGE;
+ }
+
+ return MXIT_CMD_UNKNOWN;
+}
+
+
+/*------------------------------------------------------------------------
+ * Tokenize a MXit Command string into a <key,value> map.
+ *
+ * @param cmd The MXit command string
+ * @return The <key,value> hash-map, or NULL on error.
+ */
+static GHashTable* command_tokenize(char* cmd)
+{
+ GHashTable* hash = NULL;
+ gchar** parts;
+ gchar* part;
+ int i = 0;
+
+#ifdef MXIT_DEBUG_COMMANDS
+ purple_debug_info(MXIT_PLUGIN_ID, "command: '%s'\n", cmd);
+#endif
+
+ /* explode the command into parts */
+ parts = g_strsplit(cmd, "|", 0);
+
+ hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+
+ /* now break part into a key & value */
+ while ((part = parts[i]) != NULL) {
+ char* value;
+
+ value = strchr(parts[i], '='); /* find start of value */
+ if (value != NULL) {
+ *value = '\0';
+ value++;
+ }
+
+#ifdef MXIT_DEBUG_COMMANDS
+ purple_debug_info(MXIT_PLUGIN_ID, " key='%s' value='%s'\n", parts[i], value);
+#endif
+
+ g_hash_table_insert(hash, g_strdup(parts[i]), g_strdup(value));
+
+ i++;
+ }
+
+ g_strfreev(parts);
+
+ return hash;
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a ClearScreen MXit command.
+ *
+ * @param session The MXit session object
+ * @param from The sender of the message.
+ */
+static void command_clearscreen(struct MXitSession* session, const char* from)
+{
+ PurpleConversation *conv;
+
+ conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, from, session->acc);
+ if (conv == NULL) {
+ purple_debug_error(MXIT_PLUGIN_ID, "Conversation with '%s' not found\n", from);
+ return;
+ }
+
+ purple_conversation_clear_message_history(conv); // TODO: This doesn't actually clear the screen.
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a Reply MXit command.
+ *
+ * @param mx The received message data object
+ * @param hash The MXit command <key,value> map
+ */
+static void command_reply(struct RXMsgData* mx, GHashTable* hash)
+{
+ char* replymsg;
+ char* selmsg;
+
+ selmsg = g_hash_table_lookup(hash, "selmsg"); /* find the selection message */
+ replymsg = g_hash_table_lookup(hash, "replymsg"); /* find the reply message */
+ if ((selmsg) && (replymsg)) {
+ gchar* seltext = g_markup_escape_text(purple_url_decode(selmsg), -1);
+ gchar* replytext = g_markup_escape_text(purple_url_decode(replymsg), -1);
+
+ mxit_add_html_link( mx, replytext, seltext );
+
+ g_free(seltext);
+ g_free(replytext);
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a PlatformRequest MXit command.
+ *
+ * @param hash The MXit command <key,value> map
+ * @param msg The message to display (as generated so far)
+ */
+static void command_platformreq(GHashTable* hash, GString* msg)
+{
+ gchar* text = NULL;
+ char* selmsg;
+ char* dest;
+
+ selmsg = g_hash_table_lookup(hash, "selmsg"); /* find the selection message */
+ if (selmsg) {
+ text = g_markup_escape_text(purple_url_decode(selmsg), -1);
+ }
+
+ dest = g_hash_table_lookup(hash, "dest"); /* find the destination */
+ if (dest) {
+ g_string_append_printf(msg, "<a href=\"%s\">%s</a>", purple_url_decode(dest), (text) ? text : "Download"); /* add link to display message */
+ }
+
+ if (text)
+ g_free(text);
+}
+
+
+/*------------------------------------------------------------------------
+ * Process an inline image MXit command.
+ *
+ * @param mx The received message data object
+ * @param hash The MXit command <key,value> map
+ * @param msg The message to display (as generated so far)
+ */
+static void command_image(struct RXMsgData* mx, GHashTable* hash, GString* msg)
+{
+ const char* img;
+ const char* reply;
+ guchar* rawimg;
+ char link[256];
+ gsize rawimglen;
+ int imgid;
+
+ img = g_hash_table_lookup(hash, "dat");
+ if (img) {
+ rawimg = purple_base64_decode(img, &rawimglen);
+ //purple_util_write_data_to_file_absolute("/tmp/mxitinline.png", (char*) rawimg, rawimglen);
+ imgid = purple_imgstore_add_with_id(rawimg, rawimglen, NULL);
+ g_snprintf(link, sizeof(link), "<img id=\"%i\">", imgid);
+ g_string_append_printf(msg, "%s", link);
+ mx->flags |= PURPLE_MESSAGE_IMAGES;
+ }
+ else {
+ img = g_hash_table_lookup(hash, "src");
+ if (img) {
+ struct ii_url_request* iireq;
+
+ iireq = g_new0(struct ii_url_request,1);
+ iireq->url = g_strdup(purple_url_decode(img));
+ iireq->mx = mx;
+
+ g_string_append_printf(msg, "%s%s>", MXIT_II_TAG, iireq->url);
+ mx->got_img = TRUE;
+
+ /* lets first see if we dont have the inline image already in cache */
+ if (g_hash_table_lookup(mx->session->iimages, iireq->url)) {
+ /* inline image found in the cache, so we do not have to request it from the web */
+ g_free(iireq);
+ }
+ else {
+ /* send the request for the inline image */
+ purple_debug_info(MXIT_PLUGIN_ID, "sending request for inline image '%s'\n", iireq->url);
+
+ /* request the image (reference: "libpurple/util.h") */
+ purple_util_fetch_url_request(iireq->url, TRUE, NULL, TRUE, NULL, FALSE, mxit_cb_ii_returned, iireq);
+ mx->img_count++;
+ }
+ }
+ }
+
+ /* if this is a clickable image, show a click link */
+ reply = g_hash_table_lookup(hash, "replymsg");
+ if (reply) {
+ g_string_append_printf(msg, "\n");
+ mxit_add_html_link(mx, reply, "click here");
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a received MXit Command message.
+ *
+ * @param mx The received message data object
+ * @param message The message text
+ * @return The length of the command
+ */
+//void mxit_command_received(struct MXitSession* session, const char* from, char* message, time_t timestamp)
+int mxit_parse_command(struct RXMsgData* mx, char* message)
+{
+ GHashTable* hash = NULL;
+ char* start;
+ char* end;
+
+ /* ensure that this is really a command */
+ if ( ( message[0] != ':' ) || ( message[1] != ':' ) ) {
+ /* this is not a command */
+ return 0;
+ }
+
+ start = message + 2;
+ end = strstr(start, ":");
+ if (end) {
+ /* end of a command found */
+ *end = '\0'; /* terminate command string */
+
+ hash = command_tokenize(start); /* break into <key,value> pairs */
+ if (hash) {
+ MXitCommandType type = command_type(hash);
+
+ switch (type) {
+ case MXIT_CMD_CLRSCR :
+ command_clearscreen(mx->session, mx->from);
+ break;
+ case MXIT_CMD_REPLY :
+ command_reply(mx, hash);
+ break;
+ case MXIT_CMD_PLATREQ :
+ command_platformreq(hash, mx->msg);
+ break;
+ case MXIT_CMD_IMAGE :
+ command_image(mx, hash, mx->msg);
+ break;
+ default :
+ /* command unknown, or not currently supported */
+ break;
+ }
+ g_hash_table_destroy(hash);
+ }
+ *end = ':';
+
+ return end - message;
+ }
+ else {
+ return 0;
+ }
+}
diff --git a/libpurple/protocols/mxit/formcmds.h b/libpurple/protocols/mxit/formcmds.h
new file mode 100644
index 0000000000..ce04803b40
--- /dev/null
+++ b/libpurple/protocols/mxit/formcmds.h
@@ -0,0 +1,35 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- MXit Forms & Commands --
+ *
+ * Andrew Victor <libpurple@mxit.com>
+ *
+ * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
+ * <http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#ifndef _MXIT_FORMCMDS_H_
+#define _MXIT_FORMCMDS_H_
+
+#include "mxit.h"
+
+
+int mxit_parse_command(struct RXMsgData* mx, char* message);
+
+
+#endif /* _MXIT_FORMCMDS_H_ */
diff --git a/libpurple/protocols/mxit/http.c b/libpurple/protocols/mxit/http.c
new file mode 100644
index 0000000000..875e9ec2e8
--- /dev/null
+++ b/libpurple/protocols/mxit/http.c
@@ -0,0 +1,331 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- MXit client protocol implementation --
+ *
+ * Pieter Loubser <libpurple@mxit.com>
+ *
+ * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
+ * <http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include "purple.h"
+
+#include "mxit.h"
+#include "protocol.h"
+#include "http.h"
+
+
+/* HTTP constants */
+#define HTTP_11_200_OK "HTTP/1.1 200 OK\r\n"
+#define HTTP_11_100_CONT "HTTP/1.1 100 Continue\r\n"
+#define HTTP_11_SEPERATOR "\r\n\r\n"
+#define HTTP_CONTENT_LEN "Content-Length: "
+
+
+/* define to enable HTTP debugging */
+#define DEBUG_HTTP
+
+
+/*------------------------------------------------------------------------
+ * This will freeup the memory used by a HTTP request structure
+ *
+ * @param req The HTTP structure's resources should be freed up
+ */
+static void free_http_request( struct http_request* req )
+{
+ g_free( req->host );
+ g_free( req->data );
+ g_free( req );
+}
+
+
+/*------------------------------------------------------------------------
+ * Write the request to the HTTP server.
+ *
+ * @param fd The file descriptor
+ * @param pktdata The packet data
+ * @param pktlen The length of the packet data
+ * @return Return -1 on error, otherwise 0
+ */
+static int mxit_http_raw_write( int fd, const char* pktdata, int pktlen )
+{
+ int written;
+ int res;
+
+ written = 0;
+ while ( written < pktlen ) {
+ res = write( fd, &pktdata[written], pktlen - written );
+ if ( res <= 0 ) {
+ /* error on socket */
+ if ( errno == EAGAIN )
+ continue;
+
+ purple_debug_error( MXIT_PLUGIN_ID, "Error while writing packet to HTTP server (%i)\n", res );
+ return -1;
+ }
+ written += res;
+ }
+
+ return 0;
+}
+
+
+/*------------------------------------------------------------------------
+ * Callback when data is received from the HTTP server.
+ *
+ * @param user_data The MXit session object
+ * @param source The file-descriptor on which data was received
+ * @param cond Condition which caused the callback (PURPLE_INPUT_READ)
+ */
+static void mxit_cb_http_read( gpointer user_data, gint source, PurpleInputCondition cond )
+{
+ struct MXitSession* session = (struct MXitSession*) user_data;
+ char buf[256];
+ int buflen;
+ char* body;
+ int bodylen;
+ char* ch;
+ int len;
+ char* tmp;
+ int res;
+ char* next;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_http_read\n" );
+
+ if ( session->rx_state == RX_STATE_RLEN ) {
+ /* we are reading in the HTTP headers */
+
+ /* copy partial headers if we have any part saved */
+ memcpy( buf, session->rx_dbuf, session->rx_i );
+ buflen = session->rx_i;
+
+ /* read bytes from the socket */
+ len = read( session->fd, buf + buflen, sizeof( buf ) - buflen );
+ if ( len <= 0 ) {
+ /* connection has been terminated, or error occured */
+ goto done;
+ }
+
+//nextpacket:
+
+#ifdef DEBUG_HTTP
+ purple_debug_info( MXIT_PLUGIN_ID, "HTTP POST READ 1: (%i)\n", len );
+ dump_bytes( session, buf + buflen, len );
+#endif
+
+ /* see if we have all the HTTP headers yet */
+ ch = strstr( buf, HTTP_11_SEPERATOR );
+ if ( !ch ) {
+ /* we need to wait for more input, so save what we have */
+ session->rx_i = buflen + len;
+ memcpy( session->rx_dbuf, buf, session->rx_i );
+ return;
+ }
+ buflen += len;
+
+ /* we have the header's end now skip over the http seperator to get the body offset */
+ ch += strlen( HTTP_11_SEPERATOR );
+ *(ch - 1) = '\0';
+ body = ch;
+
+ res = buflen - ( ch - buf );
+ if ( res > 0 ) {
+ /* we read more bytes than just the header so copy it over */
+ memcpy( session->rx_dbuf, ch, res );
+ session->rx_i = res;
+ }
+ else {
+ session->rx_i = 0;
+ }
+
+ /* test for a good response */
+ if ( ( strncmp( buf, HTTP_11_200_OK, strlen( HTTP_11_200_OK ) ) != 0 ) && ( strncmp( buf, HTTP_11_100_CONT, strlen( HTTP_11_100_CONT ) ) != 0 ) ) {
+ /* bad result */
+ purple_debug_error( MXIT_PLUGIN_ID, "HTTP error: %s\n", ch );
+ goto done;
+ }
+
+ /* find the content-length */
+ ch = (char*) purple_strcasestr( buf, HTTP_CONTENT_LEN );
+ if ( !ch ) {
+ /* bad request. it does not contain a content-length header */
+ purple_debug_error( MXIT_PLUGIN_ID, "HTTP reply received without content-length header (ignoring packet)\n" );
+ goto done;
+ }
+
+ /* parse the content-length */
+ ch += strlen( HTTP_CONTENT_LEN );
+ tmp = strchr( ch, '\r' );
+ if ( !tmp ) {
+ purple_debug_error( MXIT_PLUGIN_ID, "Received bad HTTP reply packet (ignoring packet)\n" );
+ goto done;
+ }
+ tmp = g_strndup( ch, tmp - ch );
+ bodylen = atoi( tmp );
+ g_free( tmp );
+ tmp = NULL;
+
+ if ( buflen > ( ( body - buf ) + bodylen ) ) {
+ /* we have a second packet here */
+ next = body + bodylen;
+ session->rx_res = 0;
+ }
+ else {
+ session->rx_res = bodylen - session->rx_i;
+ }
+
+ if ( session->rx_res == 0 ) {
+ /* we have read all the data */
+ session->rx_i = bodylen;
+ session->rx_state = RX_STATE_PROC;
+ }
+ else {
+ /* there is still some data outstanding */
+ session->rx_state = RX_STATE_DATA;
+ }
+ }
+ else if ( session->rx_state == RX_STATE_DATA ) {
+ /* we are reading the HTTP content (body) */
+
+ /* read bytes from the socket */
+ len = read( session->fd, &session->rx_dbuf[session->rx_i], session->rx_res );
+ if ( len <= 0 ) {
+ /* connection has been terminated, or error occured */
+ goto done;
+ }
+
+#ifdef DEBUG_HTTP
+ purple_debug_info( MXIT_PLUGIN_ID, "HTTP POST READ 2: (%i)\n", len );
+ dump_bytes( session, &session->rx_dbuf[session->rx_i], len );
+#endif
+ session->rx_i += len;
+ session->rx_res -= len;
+
+ if ( session->rx_res == 0 ) {
+ /* ok, so now we have read in the whole packet */
+ session->rx_state = RX_STATE_PROC;
+ }
+ }
+
+ if ( session->rx_state == RX_STATE_PROC ) {
+ mxit_parse_packet( session );
+
+#if 0
+ if ( next ) {
+ /* there is another packet of which we read some data */
+
+ /* reset input */
+ session->rx_state = RX_STATE_RLEN;
+ session->rx_lbuf[0] = '\0';
+ session->rx_i = 0;
+ session->rx_res = 0;
+
+ /* move read data */
+ len = next - buf;
+ buflen = len;
+ memcpy( buf, next, len );
+ goto nextpacket;
+ }
+#endif
+
+ /* we are done */
+ goto done;
+ }
+
+ return;
+done:
+ close( session->fd );
+ purple_input_remove( session->http_handler );
+ session->http_handler = 0;
+}
+
+
+/*------------------------------------------------------------------------
+ * Callback invoked once the connection has been established to the HTTP server,
+ * or on connection failure.
+ *
+ * @param user_data The MXit session object
+ * @param source The file-descriptor associated with the connection
+ * @param error_message Message explaining why the connection failed
+ */
+static void mxit_cb_http_connect( gpointer user_data, gint source, const gchar* error_message )
+{
+ struct http_request* req = (struct http_request*) user_data;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_http_connect\n" );
+
+ /* source is the file descriptor of the new connection */
+ if ( source < 0 ) {
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_http_connect failed: %s\n", error_message );
+ purple_connection_error( req->session->con, _( "Unable to connect to the mxit HTTP server. Please check your server server settings." ) );
+ return;
+ }
+
+ /* we now have an open and active TCP connection to the mxit server */
+ req->session->fd = source;
+
+ /* reset the receive buffer */
+ req->session->rx_state = RX_STATE_RLEN;
+ req->session->rx_lbuf[0] = '\0';
+ req->session->rx_i = 0;
+ req->session->rx_res = 0;
+
+ /* start listening on the open connection for messages from the server (reference: "libpurple/eventloop.h") */
+ req->session->http_handler = purple_input_add( req->session->fd, PURPLE_INPUT_READ, mxit_cb_http_read, req->session );
+
+ /* actually send the request to the HTTP server */
+ mxit_http_raw_write( req->session->fd, req->data, req->datalen );
+
+ /* free up resources */
+ free_http_request( req );
+ req = NULL;
+}
+
+
+/*------------------------------------------------------------------------
+ * Create HTTP connection for sending a HTTP request
+ *
+ * @param session The MXit session object
+ * @param host The server name to connect to
+ * @param port The port number to connect to
+ * @param data The HTTP request data (including HTTP headers etc.)
+ * @param datalen The HTTP request data length
+ */
+void mxit_http_send_request( struct MXitSession* session, char* host, int port, const char* data, int datalen )
+{
+ PurpleProxyConnectData* con = NULL;
+ struct http_request* req;
+
+ /* build the http request */
+ req = g_new0( struct http_request, 1 );
+ req->session = session;
+ req->host = host;
+ req->port = port;
+ req->data = g_malloc0( datalen );
+ memcpy( req->data, data, datalen );
+ req->datalen = datalen;
+
+ /* open connection to the HTTP server */
+ con = purple_proxy_connect( NULL, session->acc, host, port, mxit_cb_http_connect, req );
+}
+
diff --git a/libpurple/protocols/mxit/http.h b/libpurple/protocols/mxit/http.h
new file mode 100644
index 0000000000..03808fff58
--- /dev/null
+++ b/libpurple/protocols/mxit/http.h
@@ -0,0 +1,47 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- MXit client protocol implementation --
+ *
+ * Pieter Loubser <libpurple@mxit.com>
+ *
+ * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
+ * <http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+
+#ifndef _MXIT_HTTP_H_
+#define _MXIT_HTTP_H_
+
+
+
+struct http_request
+{
+ struct MXitSession* session;
+ char* host;
+ int port;
+ char* data;
+ int datalen;
+};
+
+
+void mxit_http_send_request( struct MXitSession* session, char* host, int port, const char* data, int datalen );
+
+
+
+#endif /* _MXIT_HTTP_H_ */
+
diff --git a/libpurple/protocols/mxit/login.c b/libpurple/protocols/mxit/login.c
new file mode 100644
index 0000000000..1e7304df9d
--- /dev/null
+++ b/libpurple/protocols/mxit/login.c
@@ -0,0 +1,789 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- MXit user login functionality --
+ *
+ * Pieter Loubser <libpurple@mxit.com>
+ *
+ * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
+ * <http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "purple.h"
+
+#include "protocol.h"
+#include "mxit.h"
+#include "cipher.h"
+#include "login.h"
+#include "profile.h"
+
+/* requesting captcha size */
+#define MXIT_CAPTCHA_HEIGHT 50
+#define MXIT_CAPTCHA_WIDTH 150
+
+
+/* prototypes */
+static void mxit_register_view( struct MXitSession* session );
+static void get_clientinfo( struct MXitSession* session );
+
+
+/*------------------------------------------------------------------------
+ * Create a new mxit session object
+ *
+ * @return The MXit session object
+ */
+static struct MXitSession* mxit_create_object( PurpleAccount* account )
+{
+ struct MXitSession* session = NULL;
+ PurpleConnection* con = NULL;
+
+ /* currently the wapsite does not handle a '+' in front of the username (mxitid) so we just strip it */
+ if ( account->username[0] == '+' ) {
+ char* fixed;
+
+ /* cut off the '+' */
+ fixed = g_strdup( &account->username[1] );
+ purple_account_set_username( account, fixed );
+ g_free( fixed );
+ }
+
+ session = g_new0( struct MXitSession, 1 );
+
+ /* configure the connection (reference: "libpurple/connection.h") */
+ con = purple_account_get_connection( account );
+ con->proto_data = session;
+ con->flags |= PURPLE_CONNECTION_NO_BGCOLOR | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_HTML;
+ session->con = con;
+
+ /* add account */
+ session->acc = account;
+
+ /* configure the session (reference: "libpurple/account.h") */
+ g_strlcpy( session->server, purple_account_get_string( account, MXIT_CONFIG_SERVER_ADDR, DEFAULT_SERVER ), sizeof( session->server ) );
+ g_strlcpy( session->http_server, purple_account_get_string( account, MXIT_CONFIG_HTTPSERVER, DEFAULT_HTTP_SERVER ), sizeof( session->http_server ) );
+ session->port = purple_account_get_int( account, MXIT_CONFIG_SERVER_PORT, DEFAULT_PORT );
+ g_strlcpy( session->distcode, purple_account_get_string( account, MXIT_CONFIG_DISTCODE, "" ), sizeof( session->distcode ) );
+ g_strlcpy( session->clientkey, purple_account_get_string( account, MXIT_CONFIG_CLIENTKEY, "" ), sizeof( session->clientkey ) );
+ g_strlcpy( session->dialcode, purple_account_get_string( account, MXIT_CONFIG_DIALCODE, "" ), sizeof( session->dialcode ) );
+ session->http = purple_account_get_bool( account, MXIT_CONFIG_USE_HTTP, FALSE );
+ session->iimages = g_hash_table_new( g_str_hash, g_str_equal );
+ session->rx_state = RX_STATE_RLEN;
+ session->http_interval = MXIT_HTTP_POLL_MIN;
+ session->http_last_poll = time( NULL );
+
+ return session;
+}
+
+
+/*------------------------------------------------------------------------
+ * We now have a connection established with MXit, so we can start the
+ * login procedure
+ *
+ * @param session The MXit session object
+ */
+static void mxit_connected( struct MXitSession* session )
+{
+ int state;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_connected\n" );
+
+ session->flags |= MXIT_FLAG_CONNECTED;
+ purple_connection_update_progress( session->con, _( "Logging In..." ), 2, 4 );
+
+ /* create a timer to send a ping packet if the connection is idle */
+ session->last_tx = time( NULL );
+
+ /* encrypt the user password */
+ session->encpwd = mxit_encrypt_password( session );
+
+ state = purple_account_get_int( session->acc, MXIT_CONFIG_STATE, MXIT_STATE_LOGIN );
+ if ( state == MXIT_STATE_LOGIN ) {
+ /* create and send login packet */
+ mxit_send_login( session );
+ }
+ else {
+ if ( !session->profile ) {
+ /* we have lost the session profile, so ask the user to enter it again */
+ mxit_register_view( session );
+ }
+ else {
+ /* create and send the register packet */
+ mxit_send_register( session );
+ }
+ }
+
+ /* enable signals */
+ mxit_enable_signals( session );
+
+#ifdef MXIT_LINK_CLICK
+ /* register for uri click notification */
+ mxit_register_uri_handler();
+#endif
+
+ /* start the polling if this is a HTTP connection */
+ if ( session->http ) {
+ session->http_timer_id = purple_timeout_add_seconds( 2, mxit_manage_polling, session );
+ }
+
+ /* start the tx queue manager timer */
+ session->q_timer = purple_timeout_add_seconds( 2, mxit_manage_queue, session );
+}
+
+
+/*------------------------------------------------------------------------
+ * Callback invoked once the connection has been established to the MXit server,
+ * or on connection failure.
+ *
+ * @param user_data The MXit session object
+ * @param source The file-descriptor associated with the connection
+ * @param error_message Message explaining why the connection failed
+ */
+static void mxit_cb_connect( gpointer user_data, gint source, const gchar* error_message )
+{
+ struct MXitSession* session = (struct MXitSession*) user_data;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_connect\n" );
+
+ /* source is the file descriptor of the new connection */
+ if ( source < 0 ) {
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_connect failed: %s\n", error_message );
+ purple_connection_error( session->con, _( "Unable to connect to the mxit server. Please check your server server settings." ) );
+ return;
+ }
+
+ /* we now have an open and active TCP connection to the mxit server */
+ session->fd = source;
+
+ /* start listening on the open connection for messages from the server (reference: "libpurple/eventloop.h") */
+ session->con->inpa = purple_input_add( session->fd, PURPLE_INPUT_READ, mxit_cb_rx, session );
+
+ mxit_connected( session );
+}
+
+
+/*------------------------------------------------------------------------
+ * Attempt to establish a connection to the MXit server.
+ *
+ * @param session The MXit session object
+ */
+static void mxit_login_connect( struct MXitSession* session )
+{
+ PurpleProxyConnectData* data = NULL;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_login_connect\n" );
+
+ purple_connection_update_progress( session->con, _( "Connecting..." ), 1, 4 );
+
+ /*
+ * at this stage we have all the user's information we require
+ * for logging into MXit. we will now create a new connection to
+ * a MXit server.
+ */
+
+ if ( !session->http ) {
+ /* socket connection */
+ data = purple_proxy_connect( session->con, session->acc, session->server, session->port, mxit_cb_connect, session );
+ if ( !data ) {
+ purple_connection_error( session->con, _( "Unable to connect to the mxit server. Please check your server server settings." ) );
+ return;
+ }
+ }
+ else {
+ /* http connection */
+ mxit_connected( session );
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Register a new account with MXit
+ *
+ * @param gc The connection object
+ * @param fields This is the fields filled-in by the user
+ */
+static void mxit_cb_register_ok( PurpleConnection *gc, PurpleRequestFields *fields )
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+ struct MXitProfile* profile = session->profile;
+ const char* str;
+ const char* pin;
+ char* err = NULL;
+ int len;
+ int i;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_register_ok\n" );
+
+ if ( !PURPLE_CONNECTION_IS_VALID( gc ) ) {
+ purple_debug_error( MXIT_PLUGIN_ID, "Unable to register; account offline.\n" );
+ return;
+ }
+
+ /* nickname */
+ str = purple_request_fields_get_string( fields, "nickname" );
+ if ( ( !str ) || ( strlen( str ) < 3 ) ) {
+ err = "The nick name you entered is invalid.";
+ goto out;
+ }
+ g_strlcpy( profile->nickname, str, sizeof( profile->nickname ) );
+
+ /* birthdate */
+ str = purple_request_fields_get_string( fields, "bday" );
+ if ( ( !str ) || ( strlen( str ) < 10 ) || ( !validateDate( str ) ) ) {
+ err = "The birthday you entered is invalid. The correct format is: 'YYYY-MM-DD'.";
+ goto out;
+ }
+ g_strlcpy( profile->birthday, str, sizeof( profile->birthday ) );
+
+ /* gender */
+ if ( purple_request_fields_get_choice( fields, "male" ) == 0 )
+ profile->male = FALSE;
+ else
+ profile->male = TRUE;
+
+ /* pin */
+ pin = purple_request_fields_get_string( fields, "pin" );
+ if ( !pin ) {
+ err = "The PIN you entered is invalid.";
+ goto out;
+ }
+ len = strlen( pin );
+ if ( ( len < 7 ) || ( len > 10 ) ) {
+ err = "The PIN you entered has an invalid length [7-10].";
+ goto out;
+ }
+ for ( i = 0; i < len; i++ ) {
+ if ( !g_ascii_isdigit( pin[i] ) ) {
+ err = "The PIN is invalid. It should only consist of digits [0-9].";
+ goto out;
+ }
+ }
+ str = purple_request_fields_get_string( fields, "pin2" );
+ if ( ( !str ) || ( strcmp( pin, str ) != 0 ) ) {
+ err = "The two PINs you entered does not match.";
+ goto out;
+ }
+ g_strlcpy( profile->pin, pin, sizeof( profile->pin ) );
+
+out:
+ if ( !err ) {
+ purple_account_set_password( session->acc, session->profile->pin );
+ mxit_login_connect( session );
+ }
+ else {
+ /* show error to user */
+ mxit_popup( PURPLE_NOTIFY_MSG_ERROR, _( "Registration Error" ), _( err ) );
+ mxit_register_view( session );
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Register a new account with MXit
+ *
+ * @param gc The connection object
+ * @param fields This is the fields filled-in by the user
+ */
+static void mxit_cb_register_cancel( PurpleConnection *gc, PurpleRequestFields *fields )
+{
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_register_cancel\n" );
+
+ /* disconnect */
+ purple_account_disconnect( gc->account );
+}
+
+
+/*------------------------------------------------------------------------
+ * Show a window to the user so that he can enter his information
+ *
+ * @param session The MXit session object
+ */
+static void mxit_register_view( struct MXitSession* session )
+{
+ struct MXitProfile* profile;
+ PurpleRequestFields* fields;
+ PurpleRequestFieldGroup* group;
+ PurpleRequestField* field;
+
+ if ( !session->profile ) {
+ /* we need to create a profile object here */
+ session->profile = g_new0( struct MXitProfile, 1 );
+ }
+ profile = session->profile;
+
+ fields = purple_request_fields_new();
+ group = purple_request_field_group_new( NULL );
+ purple_request_fields_add_group( fields, group );
+
+ /* mxit login name */
+ field = purple_request_field_string_new( "loginname", _( "MXit Login Name" ), purple_account_get_username( session->acc ), FALSE );
+ purple_request_field_string_set_editable( field, FALSE );
+ purple_request_field_group_add_field( group, field );
+
+ /* nick name */
+ field = purple_request_field_string_new( "nickname", _( "Nick Name" ), profile->nickname, FALSE );
+ purple_request_field_group_add_field( group, field );
+
+ /* birthday */
+ field = purple_request_field_string_new( "bday", _( "Birthday" ), profile->birthday, FALSE );
+ purple_request_field_string_set_default_value( field, "YYYY-MM-DD" );
+ purple_request_field_group_add_field( group, field );
+
+ /* gender */
+ field = purple_request_field_choice_new( "male", _( "Gender" ), ( profile->male ) ? 1 : 0 );
+ purple_request_field_choice_add( field, _( "Female" ) ); /* 0 */
+ purple_request_field_choice_add( field, _( "Male" ) ); /* 1 */
+ purple_request_field_group_add_field( group, field );
+
+ /* pin */
+ field = purple_request_field_string_new( "pin", _( "PIN" ), profile->pin, FALSE );
+ purple_request_field_string_set_masked( field, TRUE );
+ purple_request_field_group_add_field( group, field );
+ field = purple_request_field_string_new( "pin2", _( "Verify PIN" ), "", FALSE );
+ purple_request_field_string_set_masked( field, TRUE );
+ purple_request_field_group_add_field( group, field );
+
+ /* show the form to the user to complete */
+ purple_request_fields( session->con, _( "Register New MXit Account" ), _( "Register New MXit Account" ), _( "Please fill in the following fields:" ), fields, _( "OK" ), G_CALLBACK( mxit_cb_register_ok ), _( "Cancel" ), G_CALLBACK( mxit_cb_register_cancel ), session->acc, NULL, NULL, session->con );
+}
+
+
+/*------------------------------------------------------------------------
+ * Callback function invoked once the Authorization information has been submitted
+ * to the MXit WAP site.
+ *
+ * @param url_data libPurple internal object (see purple_util_fetch_url_request)
+ * @param user_data The MXit session object
+ * @param url_text The data returned from the WAP site
+ * @param len The length of the data returned
+ * @param error_message Descriptive error message
+ */
+static void mxit_cb_clientinfo2( PurpleUtilFetchUrlData* url_data, gpointer user_data, const gchar* url_text, gsize len, const gchar* error_message )
+{
+ struct MXitSession* session = (struct MXitSession*) user_data;
+ gchar** parts;
+ gchar** host;
+ int state;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_clientinfo_cb2\n" );
+
+#ifdef DEBUG_PROTOCOL
+ purple_debug_info( MXIT_PLUGIN_ID, "HTTP RESPONSE: '%s'\n", url_text );
+#endif
+
+ if ( !url_text ) {
+ /* no reply from the WAP site */
+ purple_connection_error( session->con, _( "Error contacting the MXit WAP site. Please try again later." ) );
+ return;
+ }
+
+ /* explode the response from the WAP site into an array */
+ parts = g_strsplit( url_text, ";", 15 );
+
+ if ( !parts ) {
+ /* wapserver error */
+ purple_connection_error( session->con, _( "MXit is currently unable to process the request. Please try again later." ) );
+ return;
+ }
+
+ /* check wapsite return code */
+ switch ( parts[0][0] ) {
+ case '0' :
+ /* valid reply! */
+ break;
+ case '1' :
+ purple_connection_error( session->con, _( "Wrong security code entered. Please try again later." ) );
+ return;
+ case '2' :
+ purple_connection_error( session->con, _( "Your session has expired. Please try again later." ) );
+ return;
+ case '5' :
+ purple_connection_error( session->con, _( "Invalid country selected. Please try again." ) );
+ return;
+ case '6' :
+ purple_connection_error( session->con, _( "Username is not registered. Please register first." ) );
+ return;
+ case '7' :
+ purple_connection_error( session->con, _( "Username is already registered. Please choose another username." ) );
+ /* this user's account already exists, so we need to change the registration login flag to be login */
+ purple_account_set_int( session->acc, MXIT_CONFIG_STATE, MXIT_STATE_LOGIN );
+ return;
+ case '3' :
+ case '4' :
+ default :
+ purple_connection_error( session->con, _( "Internal error. Please try again later." ) );
+ return;
+ }
+
+ /* now parse and split the distribution code and the client key */
+ g_strlcpy( session->distcode, &parts[1][2], 36 + 1 );
+ g_strlcpy( session->clientkey, &parts[1][38], 8 + 1 );
+
+ /* get the dial code for the client */
+ g_strlcpy( session->dialcode, parts[4], sizeof( session->dialcode ) );
+
+ /* parse the proxy server address and port number */
+ host = g_strsplit( parts[2], ":", 4 );
+ g_strlcpy( session->server, &host[1][2], sizeof( session->server ) );
+ session->port = atoi( &host[2][0] );
+
+ /* parse the http proxy server address and port number */
+ g_strlcpy( session->http_server, parts[3], sizeof( session->http_server ) );
+
+ purple_debug_info( MXIT_PLUGIN_ID, "distcode='%s', clientkey='%s', dialcode='%s'\n", session->distcode, session->clientkey, session->dialcode );
+ purple_debug_info( MXIT_PLUGIN_ID, "sock_server='%s', http_server='%s', port='%i', cc='%s'\n", session->server, session->http_server, session->port, parts[11] );
+
+ /* save the information (reference: "libpurple/account.h") */
+ purple_account_set_string( session->acc, MXIT_CONFIG_DISTCODE, session->distcode );
+ purple_account_set_string( session->acc, MXIT_CONFIG_CLIENTKEY, session->clientkey );
+ purple_account_set_string( session->acc, MXIT_CONFIG_DIALCODE, session->dialcode );
+ purple_account_set_string( session->acc, MXIT_CONFIG_SERVER_ADDR, session->server );
+ purple_account_set_int( session->acc, MXIT_CONFIG_SERVER_PORT, session->port );
+ purple_account_set_string( session->acc, MXIT_CONFIG_HTTPSERVER, session->http_server );
+
+ /* update the state */
+ state = purple_account_get_int( session->acc, MXIT_CONFIG_STATE, MXIT_STATE_LOGIN );
+ if ( state == MXIT_STATE_REGISTER1 )
+ purple_account_set_int( session->acc, MXIT_CONFIG_STATE, MXIT_STATE_REGISTER2 );
+
+ /* freeup the memory */
+ g_strfreev( host );
+ g_strfreev( parts );
+
+ if ( state == MXIT_STATE_LOGIN ) {
+ /* now we can continue with the login process */
+ mxit_login_connect( session );
+ }
+ else {
+ /* the user is registering so we need to get more information from him/her first to complete the process */
+ mxit_register_view( session );
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Free up the data associated with the Authorization process.
+ *
+ * @param data The data object to free
+ */
+static void free_logindata( struct login_data* data )
+{
+ if ( !data )
+ return;
+
+ /* free up the login resources */
+ g_free( data->wapserver );
+ g_free( data->sessionid );
+ g_free( data->captcha );
+ g_free( data->cc );
+ g_free( data->locale );
+ g_free( data );
+}
+
+
+/*------------------------------------------------------------------------
+ * This function is called when the user accepts the Authorization form.
+ *
+ * @param gc The connection object
+ * @param fields The list of fields in the accepted form
+ */
+static void mxit_cb_captcha_ok( PurpleConnection* gc, PurpleRequestFields* fields )
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+ PurpleUtilFetchUrlData* url_data;
+ PurpleRequestField* field;
+ const char* captcha_resp;
+ GList* entries;
+ GList* entry;
+ char* url;
+ int state;
+
+ /* get the captcha response */
+ captcha_resp = purple_request_fields_get_string( fields, "code" );
+ if ( ( captcha_resp == NULL ) || ( captcha_resp[0] == '\0' ) ) {
+ /* the user did not fill in the captcha */
+ mxit_popup( PURPLE_NOTIFY_MSG_ERROR, _( "Error" ), _( "You did not enter the security code" ) );
+ free_logindata( session->logindata );
+ purple_account_disconnect( session->acc );
+ return;
+ }
+
+ /* get chosen country */
+ field = purple_request_fields_get_field( fields, "country" );
+ entries = purple_request_field_list_get_selected( field );
+ entry = g_list_first( entries );
+ session->logindata->cc = purple_request_field_list_get_data( field, entry->data );
+ purple_account_set_string( session->acc, MXIT_CONFIG_COUNTRYCODE, session->logindata->cc );
+
+ /* get chosen language */
+ field = purple_request_fields_get_field( fields, "locale" );
+ entries = purple_request_field_list_get_selected( field );
+ entry = g_list_first( entries );
+ session->logindata->locale = purple_request_field_list_get_data( field, entry->data );
+ purple_account_set_string( session->acc, MXIT_CONFIG_LOCALE, session->logindata->locale );
+
+#ifdef DEBUG_PROTOCOL
+ purple_debug_info( MXIT_PLUGIN_ID, "cc='%s', locale='%s', captcha='%s'\n", session->logindata->cc, session->logindata->locale, captcha_resp );
+#endif
+
+ /* get state */
+ state = purple_account_get_int( session->acc, MXIT_CONFIG_STATE, MXIT_STATE_LOGIN );
+
+ url = g_strdup_printf( "%s?type=getpid&sessionid=%s&login=%s&ver=%s&clientid=%s&cat=%s&chalresp=%s&cc=%s&loc=%s&path=%i&brand=%s&model=%s&h=%i&w=%i&ts=%li",
+ session->logindata->wapserver, session->logindata->sessionid, purple_url_encode( session->acc->username ), MXIT_CP_RELEASE, MXIT_CLIENT_ID, MXIT_CP_ARCH,
+ captcha_resp, session->logindata->cc, session->logindata->locale, ( state == MXIT_STATE_REGISTER1 ) ? 0 : 1, MXIT_CP_PLATFORM, MXIT_CP_OS,
+ MXIT_CAPTCHA_HEIGHT, MXIT_CAPTCHA_WIDTH, time( NULL ) );
+ url_data = purple_util_fetch_url_request( url, TRUE, MXIT_HTTP_USERAGENT, TRUE, NULL, FALSE, mxit_cb_clientinfo2, session );
+
+#ifdef DEBUG_PROTOCOL
+ purple_debug_info( MXIT_PLUGIN_ID, "HTTP REQUEST: '%s'\n", url );
+#endif
+ g_free( url );
+
+ /* free up the login resources */
+ free_logindata( session->logindata );
+}
+
+
+/*------------------------------------------------------------------------
+ * This function is called when the user cancels the Authorization form.
+ *
+ * @param gc The connection object
+ * @param fields The list of fields in the cancelled form
+ */
+static void mxit_cb_captcha_cancel( PurpleConnection* gc, PurpleRequestFields* fields )
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+
+ /* free up the login resources */
+ free_logindata( session->logindata );
+
+ /* we cannot continue, so we disconnect this account */
+ purple_account_disconnect( session->acc );
+}
+
+
+/*------------------------------------------------------------------------
+ * Callback function invoked once the client information has been retrieved from
+ * the MXit WAP site. Display page where user can select their authorization information.
+ *
+ * @param url_data libPurple internal object (see purple_util_fetch_url_request)
+ * @param user_data The MXit session object
+ * @param url_text The data returned from the WAP site
+ * @param len The length of the data returned
+ * @param error_message Descriptive error message
+ */
+static void mxit_cb_clientinfo1( PurpleUtilFetchUrlData* url_data, gpointer user_data, const gchar* url_text, gsize len, const gchar* error_message )
+{
+ struct MXitSession* session = (struct MXitSession*) user_data;
+ struct login_data* logindata;
+ PurpleRequestFields* fields;
+ PurpleRequestFieldGroup* group = NULL;
+ PurpleRequestField* field = NULL;
+ gchar** parts;
+ gchar** countries;
+ gchar** locales;
+ int i;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_clientinfo_cb1\n" );
+
+#ifdef DEBUG_PROTOCOL
+ purple_debug_info( MXIT_PLUGIN_ID, "RESPONSE: %s\n", url_text );
+#endif
+
+ if ( !url_text ) {
+ /* no reply from the WAP site */
+ purple_connection_error( session->con, _( "Error contacting the MXit WAP site. Please try again later." ) );
+ return;
+ }
+
+ /* explode the response from the WAP site into an array */
+ parts = g_strsplit( url_text, ";", 15 );
+
+ if ( ( !parts ) || ( parts[0][0] != '0' ) ) {
+ /* server could not find the user */
+ purple_connection_error( session->con, _( "MXit is currently unable to process the request. Please try again later." ) );
+ return;
+ }
+
+ /* save received settings */
+ logindata = g_new0( struct login_data, 1 );
+ logindata->wapserver = g_strdup( parts[1] );
+ logindata->sessionid = g_strdup( parts[2] );
+ session->logindata = logindata;
+
+ /* now generate the popup requesting the user for action */
+
+ fields = purple_request_fields_new();
+ group = purple_request_field_group_new( NULL );
+ purple_request_fields_add_group( fields, group );
+
+ /* add the captcha */
+ logindata->captcha = purple_base64_decode( parts[3], &logindata->captcha_size );
+ field = purple_request_field_image_new( "capcha", _( "Security Code" ), (gchar*) logindata->captcha, logindata->captcha_size );
+ purple_request_field_group_add_field( group, field );
+
+ /* ask for input */
+ field = purple_request_field_string_new( "code", _( "Enter Security Code" ), NULL, FALSE );
+ purple_request_field_group_add_field( group, field );
+
+ /* choose your country, but be careful, we already know your IP! ;-) */
+ countries = g_strsplit( parts[4], ",", 500 );
+ field = purple_request_field_list_new( "country", _( "Your Country" ) );
+ purple_request_field_list_set_multi_select( field, FALSE );
+ for ( i = 0; countries[i]; i++ ) {
+ gchar** country;
+
+ country = g_strsplit( countries[i], "|", 2 );
+ if ( !country ) {
+ /* oops, this is not good, time to bail */
+ break;
+ }
+ purple_request_field_list_add( field, country[1], g_strdup( country[0] ) );
+ if ( strcmp( country[1], parts[6] ) == 0 ) {
+ /* based on the user's ip, this is his current country code, so we default to it */
+ purple_request_field_list_add_selected( field, country[1] );
+ }
+ g_strfreev( country );
+ }
+ purple_request_field_group_add_field( group, field );
+
+ /* choose your language */
+ locales = g_strsplit( parts[5], ",", 200 );
+ field = purple_request_field_list_new( "locale", _( "Your Language" ) );
+ purple_request_field_list_set_multi_select( field, FALSE );
+ for ( i = 0; locales[i]; i++ ) {
+ gchar** locale;
+
+ locale = g_strsplit( locales[i], "|", 2 );
+ if ( !locale ) {
+ /* oops, this is not good, time to bail */
+ break;
+ }
+ purple_request_field_list_add( field, locale[1], g_strdup( locale[0] ) );
+ g_strfreev( locale );
+ }
+ purple_request_field_list_add_selected( field, "English" );
+ purple_request_field_group_add_field( group, field );
+
+ /* display the form to the user and wait for his/her input */
+ purple_request_fields( session->con, "MXit", _( "MXit Authorization" ), _( "MXit account validation" ), fields,
+ _( "Continue" ), G_CALLBACK( mxit_cb_captcha_ok ), _( "Cancel" ), G_CALLBACK( mxit_cb_captcha_cancel ), session->acc, NULL, NULL, session->con );
+
+ /* freeup the memory */
+ g_strfreev( parts );
+}
+
+
+/*------------------------------------------------------------------------
+ * Initiate a request for the client information (distribution code, client key, etc)
+ * required for logging in from the MXit WAP site.
+ *
+ * @param session The MXit session object
+ */
+static void get_clientinfo( struct MXitSession* session )
+{
+ PurpleUtilFetchUrlData* url_data;
+ const char* wapserver;
+ char* url;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "get_clientinfo\n" );
+
+ purple_connection_update_progress( session->con, _( "Retrieving User Information..." ), 0, 4 );
+
+ /* get the WAP site as was configured by the user in the advanced settings */
+ wapserver = purple_account_get_string( session->acc, MXIT_CONFIG_WAPSERVER, DEFAULT_WAPSITE );
+
+ /* reference: "libpurple/util.h" */
+ url = g_strdup_printf( "%s/res/?type=challenge&getcountries=true&getlanguage=true&getimage=true&h=%i&w=%i&ts=%li", wapserver, MXIT_CAPTCHA_HEIGHT, MXIT_CAPTCHA_WIDTH, time( NULL ) );
+ url_data = purple_util_fetch_url_request( url, TRUE, MXIT_HTTP_USERAGENT, TRUE, NULL, FALSE, mxit_cb_clientinfo1, session );
+
+#ifdef DEBUG_PROTOCOL
+ purple_debug_info( MXIT_PLUGIN_ID, "HTTP REQUEST: '%s'\n", url );
+#endif
+ g_free( url );
+}
+
+
+/*------------------------------------------------------------------------
+ * Log the user into MXit.
+ *
+ * @param account The account object
+ */
+void mxit_login( PurpleAccount* account )
+{
+ struct MXitSession* session = NULL;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_login\n" );
+
+ /* create and save a new mxit session */
+ session = mxit_create_object( account );
+
+ /*
+ * before we can login we need to have a valid distribution code and client key for authentication.
+ * if we don't have any info saved from a previous login, we need to get it from the MXit WAP site.
+ * we do cache it, so this step is only done on the very first login for each account.
+ */
+ if ( ( session->distcode == NULL ) || ( strlen( session->distcode ) == 0 ) ) {
+ /* this must be the very first login, so we need to retrieve the user information */
+ get_clientinfo( session );
+ }
+ else {
+ /* we can continue with the login */
+ mxit_login_connect( session );
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Perform a reconnect to the MXit server, and maintain same session object.
+ *
+ * @param account The account object
+ */
+void mxit_reconnect( struct MXitSession* session )
+{
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_reconnect\n" );
+
+ /* close existing connection */
+ session->flags &= ~MXIT_FLAG_CONNECTED;
+ purple_proxy_connect_cancel_with_handle( session->con );
+
+ /* perform the re-connect */
+ mxit_login_connect( session );
+}
+
+
+/*------------------------------------------------------------------------
+ * Register a new account with MXit
+ *
+ * @param acc The account object
+ */
+void mxit_register( PurpleAccount* account )
+{
+ struct MXitSession* session = NULL;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_register\n" );
+
+ /* create and save a new mxit session */
+ session = mxit_create_object( account );
+ purple_account_set_int( account, MXIT_CONFIG_STATE, MXIT_STATE_REGISTER1 );
+
+ get_clientinfo( session );
+}
+
diff --git a/libpurple/protocols/mxit/login.h b/libpurple/protocols/mxit/login.h
new file mode 100644
index 0000000000..14947b08a7
--- /dev/null
+++ b/libpurple/protocols/mxit/login.h
@@ -0,0 +1,45 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- MXit user login functionality --
+ *
+ * Pieter Loubser <libpurple@mxit.com>
+ *
+ * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
+ * <http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#ifndef _MXIT_LOGIN_H_
+#define _MXIT_LOGIN_H_
+
+
+struct login_data {
+ char* wapserver; /* direct WAP server for postback */
+ char* sessionid; /* unique session id */
+ guchar* captcha; /* actual captcha (PNG) */
+ gsize captcha_size; /* captcha size */
+ char* cc; /* country code */
+ char* locale; /* locale (language) */
+};
+
+
+void mxit_login( PurpleAccount* account );
+void mxit_register( PurpleAccount* account );
+void mxit_reconnect( struct MXitSession* session );
+
+
+#endif /* _MXIT_LOGIN_H_ */
diff --git a/libpurple/protocols/mxit/markup.c b/libpurple/protocols/mxit/markup.c
new file mode 100644
index 0000000000..553bc64be2
--- /dev/null
+++ b/libpurple/protocols/mxit/markup.c
@@ -0,0 +1,1192 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- convert between MXit and libPurple markup --
+ *
+ * Pieter Loubser <libpurple@mxit.com>
+ *
+ * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
+ * <http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "purple.h"
+
+#include "protocol.h"
+#include "mxit.h"
+#include "markup.h"
+#include "chunk.h"
+#include "formcmds.h"
+#include "roster.h"
+
+
+/* define this to enable emoticon (markup) debugging */
+#undef MXIT_DEBUG_EMO
+/* define this to enable markup conversion debugging */
+#undef MXIT_DEBUG_MARKUP
+
+
+#define MXIT_FRAME_MAGIC "MXF\x01" /* mxit emoticon magic number */
+#define MXIT_MAX_EMO_ID 16 /* maximum emoticon ID length */
+#define COLORCODE_LEN 6 /* colour code ID length */
+
+
+/* HTML tag types */
+#define MXIT_TAG_COLOR 0x01 /* font color tag */
+#define MXIT_TAG_SIZE 0x02 /* font size tag */
+#define MXIT_MAX_MSG_TAGS 90 /* maximum tags per message (pigdin hack work around) */
+
+/*
+ * a HTML tag object
+ */
+struct tag {
+ char type;
+ char* value;
+};
+
+
+#define MXIT_VIBE_MSG_COLOR "#9933FF"
+
+/* vibes */
+static const char* vibes[] = {
+ /* 0 */ "Cool Vibrations",
+ /* 1 */ "Purple Rain",
+ /* 2 */ "Polite",
+ /* 3 */ "Rock n Roll",
+ /* 4 */ "Summer Slumber",
+ /* 5 */ "Electric Razor",
+ /* 6 */ "S.O.S",
+ /* 7 */ "Jack Hammer",
+ /* 8 */ "Bumble Bee",
+ /* 9 */ "Ripple"
+};
+
+
+
+#ifdef MXIT_DEBUG_EMO
+/*------------------------------------------------------------------------
+ * Dump a byte buffer as hexadecimal to the console for debugging purposes.
+ *
+ * @param buf The data to dump
+ * @param len The length of the data
+ */
+static void hex_dump( const char* buf, int len )
+{
+ char msg[256];
+ int pos;
+ int i;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "Dumping data (%i bytes)\n", len );
+
+ memset( msg, 0x00, sizeof( msg ) );
+ pos = 0;
+
+ for ( i = 0; i < len; i++ ) {
+
+ if ( pos == 0 )
+ pos += sprintf( &msg[pos], "%04i: ", i );
+
+ pos += sprintf( &msg[pos], "0x%02X ", (unsigned char) buf[i] );
+
+ if ( i % 16 == 15 ) {
+ pos += sprintf( &msg[pos], "\n" );
+ purple_debug_info( MXIT_PLUGIN_ID, msg );
+ pos = 0;
+ }
+ else if ( i % 16 == 7 )
+ pos += sprintf( &msg[pos], " " );
+ }
+
+ if ( pos > 0 ) {
+ pos += sprintf( &msg[pos], "\n" );
+ purple_debug_info( MXIT_PLUGIN_ID, msg );
+ pos = 0;
+ }
+}
+#endif
+
+
+/*------------------------------------------------------------------------
+ * Adds a link to a message
+ *
+ * @param mx The Markup message object
+ * @param linkname This is the what will be returned when the link gets clicked
+ * @param displayname This is the name for the link which will be displayed in the UI
+ */
+void mxit_add_html_link( struct RXMsgData* mx, const char* linkname, const char* displayname )
+{
+#ifdef MXIT_LINK_CLICK
+ char retstr[256];
+ gchar* retstr64;
+ char link[256];
+ int len;
+
+ len = g_snprintf( retstr, sizeof( retstr ), "%s|%s|%s|%s|%s", MXIT_LINK_KEY, purple_account_get_username( mx->session->acc ),
+ purple_account_get_protocol_id( mx->session->acc ), mx->from, linkname );
+ retstr64 = purple_base64_encode( (const unsigned char*) retstr, len );
+ g_snprintf( link, sizeof( link ), "%s%s", MXIT_LINK_PREFIX, retstr64 );
+ g_free( retstr64 );
+
+ g_string_append_printf( mx->msg, "<a href=\"%s\">%s</a>", link, displayname );
+#else
+ g_string_append_printf( mx->msg, "<b>%s</b>", linkname );
+#endif
+}
+
+
+/*------------------------------------------------------------------------
+ * Extract an ASN.1 formatted length field from the data.
+ *
+ * @param data The source data
+ * @param size The extracted length
+ * @return The number of bytes extracted
+ */
+static unsigned int asn_getlength( const char* data, int* size )
+{
+ unsigned int len = 0;
+ unsigned char bytes;
+ unsigned char byte;
+ int i;
+
+ /* first byte specifies the number of bytes in the length */
+ bytes = ( data[0] & ~0x80 );
+ if ( bytes > sizeof( unsigned int ) ) {
+ /* file too big! */
+ return -1;
+ }
+ data++;
+
+ /* parse out the actual length */
+ for ( i = 0; i < bytes; i++ ) {
+ byte = data[i];
+ len <<= 8;
+ len += byte;
+ }
+
+ *size = len;
+ return bytes + 1;
+}
+
+
+/*------------------------------------------------------------------------
+ * Extract an ASN.1 formatted UTF-8 string field from the data.
+ *
+ * @param data The source data
+ * @param type Expected type of string
+ * @param utf8 The extracted string. Must be deallocated by caller.
+ * @return The number of bytes extracted
+ */
+static int asn_getUtf8( const char* data, char type, char** utf8 )
+{
+ int len;
+
+ /* validate the field type [1 byte] */
+ if ( data[0] != type ) {
+ /* this is not a utf-8 string! */
+ purple_debug_error( MXIT_PLUGIN_ID, "Invalid UTF-8 encoded string in ASN data (0x%02X)\n", (unsigned char) data[0] );
+ return -1;
+ }
+
+ len = data[1]; /* length field [1 bytes] */
+ *utf8 = g_malloc( len + 1 );
+ memcpy( *utf8, &data[2], len ); /* data field */
+ (*utf8)[len] = '\0';
+
+ return ( len + 2 );
+}
+
+
+/*------------------------------------------------------------------------
+ * Free data associated with a Markup message object.
+ *
+ * @param mx The Markup message object
+ */
+static void free_markupdata( struct RXMsgData* mx )
+{
+ if ( mx ) {
+ if ( mx->msg )
+ g_string_free( mx->msg, TRUE );
+ if ( mx->from )
+ g_free( mx->from );
+ g_free( mx );
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Split the message into smaller messages and send them one at a time
+ * to pidgin to be displayed on the UI
+ *
+ * @param mx The received message object
+ */
+static void mxit_show_split_message( struct RXMsgData* mx )
+{
+ const char* cont = "<font color=\"#999999\">continuing...</font>\n";
+ GString* msg = NULL;
+ char* ch = NULL;
+ int pos = 0;
+ int start = 0;
+ int l_nl = 0;
+ int l_sp = 0;
+ int l_gt = 0;
+ int stop = 0;
+ int tags = 0;
+ int segs = 0;
+ gboolean intag = FALSE;
+
+ /*
+ * awful hack to work around the awful hack in pidgin to work around GtkIMHtml's
+ * inefficient rendering of messages with lots of formatting changes.
+ * (reference: see the function pidgin_conv_write_conv() in gtkconv.c) the issue
+ * is that when you have more than 100 '<' characters in the message passed to
+ * pidgin, none of the markup (including links) are rendered and thus just dump
+ * all the text as is to the conversation window. this message dump is very
+ * confusing and makes it totally unusable. to work around this we will count
+ * the amount of tags and if its more than the pidgin threshold, we will just
+ * break the message up into smaller parts and send them seperately to pidgin.
+ * to the user it will look like multiple messages, but at least he will be able
+ * to use and understand it.
+ */
+
+ ch = mx->msg->str;
+ pos = start;
+ while ( ch[pos] ) {
+
+ if ( ch[pos] == '<' ) {
+ tags++;
+ intag = TRUE;
+ }
+ else if ( ch[pos] == '\n' ) {
+ l_nl = pos;
+ }
+ else if ( ch[pos] == '>' ) {
+ l_gt = pos;
+ intag = FALSE;
+ }
+ else if ( ch[pos] == ' ' ) {
+ /* ignore spaces inside tags */
+ if ( !intag )
+ l_sp = pos;
+ }
+ else if ( ( ch[pos] == 'w' ) && ( pos + 4 < mx->msg->len ) && ( memcmp( &ch[pos], "www.", 4 ) == 0 ) ) {
+ tags += 2;
+ }
+ else if ( ( ch[pos] == 'h' ) && ( pos + 8 < mx->msg->len ) && ( memcmp( &ch[pos], "http://", 7 ) == 0 ) ) {
+ tags += 2;
+ }
+
+ if ( tags > MXIT_MAX_MSG_TAGS ) {
+ /* we have reached the maximum amount of tags pidgin (gtk) can handle per message.
+ so its time to send what we have and then start building a new message */
+
+ /* now find the right place to break the message */
+ if ( l_nl > start ) {
+ /* break at last '\n' char */
+ stop = l_nl;
+ ch[stop] = '\0';
+ msg = g_string_new( &ch[start] );
+ ch[stop] = '\n';
+ }
+ else if ( l_sp > start ) {
+ /* break at last ' ' char */
+ stop = l_sp;
+ ch[stop] = '\0';
+ msg = g_string_new( &ch[start] );
+ ch[stop] = ' ';
+ }
+ else {
+ /* break at the last '>' char */
+ char t;
+ stop = l_gt + 1;
+ t = ch[stop];
+ ch[stop] = '\0';
+ msg = g_string_new( &ch[start] );
+ ch[stop] = t;
+ stop--;
+ }
+
+ /* build the string */
+ if ( segs )
+ g_string_prepend( msg, cont );
+
+ /* push message to pidgin */
+ serv_got_im( mx->session->con, mx->from, msg->str, mx->flags, mx->timestamp );
+ g_string_free( msg, TRUE );
+ msg = NULL;
+
+ tags = 0;
+ segs++;
+ start = stop + 1;
+ }
+
+ pos++;
+ }
+
+ if ( start != pos ) {
+ /* send the last part of the message */
+
+ /* build the string */
+ ch[pos] = '\0';
+ msg = g_string_new( &ch[start] );
+ ch[pos] = '\n';
+ if ( segs )
+ g_string_prepend( msg, cont );
+
+ /* push message to pidgin */
+ serv_got_im( mx->session->con, mx->from, msg->str, mx->flags, mx->timestamp );
+ g_string_free( msg, TRUE );
+ msg = NULL;
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Insert custom emoticons and inline images into the message (if there
+ * are any), then give the message to the UI to display to the user.
+ *
+ * @param mx The received message object
+ */
+void mxit_show_message( struct RXMsgData* mx )
+{
+ char* pos;
+ int start;
+ unsigned int end;
+ int emo_ofs;
+ char ii[128];
+ char tag[64];
+ int* img_id;
+
+ if ( mx->got_img ) {
+ /* search and replace all emoticon tags with proper image tags */
+
+ while ( ( pos = strstr( mx->msg->str, MXIT_II_TAG ) ) != NULL ) {
+ start = pos - mx->msg->str; /* offset at which MXIT_II_TAG starts */
+ emo_ofs = start + strlen( MXIT_II_TAG ); /* offset at which EMO's ID starts */
+ end = emo_ofs + 1; /* offset at which MXIT_II_TAG ends */
+
+ while ( ( end < mx->msg->len ) && ( mx->msg->str[end] != '>' ) )
+ end++;
+
+ if ( end == mx->msg->len ) /* end of emoticon tag not found */
+ break;
+
+ memset( ii, 0x00, sizeof( ii ) );
+ memcpy( ii, &mx->msg->str[emo_ofs], end - emo_ofs );
+
+ /* remove inline image tag */
+ g_string_erase( mx->msg, start, ( end - start ) + 1 );
+
+ /* find the image entry */
+ img_id = (int*) g_hash_table_lookup( mx->session->iimages, ii );
+ if ( !img_id ) {
+ /* inline image not found, so we will just skip it */
+ purple_debug_error( MXIT_PLUGIN_ID, "inline image NOT found (%s)\n", ii );
+ }
+ else {
+ /* insert img tag */
+ g_snprintf( tag, sizeof( tag ), "<img id=\"%i\">", *img_id );
+ g_string_insert( mx->msg, start, tag );
+ }
+ }
+ }
+
+#ifdef MXIT_DEBUG_MARKUP
+ purple_debug_info( MXIT_PLUGIN_ID, "Markup RX (converted): '%s'\n", mx->msg->str );
+#endif
+
+ if ( mx->processed ) {
+ /* this message has already been taken care of, so just ignore it here */
+ }
+ else if ( mx->chatid < 0 ) {
+ /* normal chat message */
+ //serv_got_im( mx->session->con, mx->from, mx->msg->str, mx->flags, mx->timestamp );
+ mxit_show_split_message( mx );
+ }
+ else {
+ /* this is a multimx message */
+ serv_got_chat_in( mx->session->con, mx->chatid, mx->from, mx->flags, mx->msg->str, mx->timestamp);
+ }
+
+ /* freeup resource */
+ free_markupdata( mx );
+}
+
+
+/*------------------------------------------------------------------------
+ * Extract the custom emoticon ID from the message.
+ *
+ * @param message The input data
+ * @param emid The extracted emoticon ID
+ */
+static void parse_emoticon_str( const char* message, char* emid )
+{
+ int i;
+
+ for ( i = 0; ( message[i] != '\0' && message[i] != '}' && i < MXIT_MAX_EMO_ID ); i++ ) {
+ emid[i] = message[i];
+ }
+
+ if ( message[i] == '\0' ) {
+ /* end of message reached, ignore the tag */
+ emid[0] = '\0';
+ }
+ else if ( i == MXIT_MAX_EMO_ID ) {
+ /* invalid tag length, ignore the tag */
+ emid[0] = '\0';
+ }
+ else
+ emid[i] = '\0';
+}
+
+
+/*------------------------------------------------------------------------
+ * Callback function invoked when a custom emoticon request to the WAP site completes.
+ *
+ * @param url_data
+ * @param user_data The Markup message object
+ * @param url_text The data returned from the WAP site
+ * @param len The length of the data returned
+ * @param error_message Descriptive error message
+ */
+static void emoticon_returned( PurpleUtilFetchUrlData* url_data, gpointer user_data, const gchar* url_text, gsize len, const gchar* error_message )
+{
+ struct RXMsgData* mx = (struct RXMsgData*) user_data;
+ const char* data = url_text;
+ unsigned int pos = 0;
+ char emo[16];
+ int id;
+ char* str;
+ int em_size = 0;
+ char* em_data = NULL;
+ char* em_id = NULL;
+ int* intptr = NULL;
+ int res;
+
+#ifdef MXIT_DEBUG_EMO
+ purple_debug_info( MXIT_PLUGIN_ID, "emoticon_returned\n" );
+#endif
+
+ if ( !url_text ) {
+ /* no reply from the WAP site */
+ purple_debug_error( MXIT_PLUGIN_ID, "Error contacting the MXit WAP site. Please try again later (emoticon).\n" );
+ goto done;
+ }
+
+#ifdef MXIT_DEBUG_EMO
+ hex_dump( data, len );
+#endif
+
+ /* parse out the emoticon */
+ pos = 0;
+
+ /* validate the binary data received from the wapsite */
+ if ( memcmp( MXIT_FRAME_MAGIC, &data[pos], strlen( MXIT_FRAME_MAGIC ) ) != 0 ) {
+ /* bad data, magic constant is wrong */
+ purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad magic)\n" );
+ goto done;
+ }
+ pos += strlen( MXIT_FRAME_MAGIC );
+
+ /* validate the image frame desc byte */
+ if ( data[pos] != '\x6F' ) {
+ /* bad frame desc */
+ purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad frame desc)\n" );
+ goto done;
+ }
+ pos++;
+
+ /* get the data length */
+ res = asn_getlength( &data[pos], &em_size );
+ if ( res <= 0 ) {
+ /* bad frame length */
+ purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad frame length)\n" );
+ goto done;
+ }
+ pos += res;
+#ifdef MXIT_DEBUG_EMO
+ purple_debug_info( MXIT_PLUGIN_ID, "read the length '%i'\n", em_size );
+#endif
+
+ /* utf-8 (emoticon name) */
+ res = asn_getUtf8( &data[pos], 0x0C, &str );
+ if ( res <= 0 ) {
+ /* bad utf-8 string */
+ purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad name string)\n" );
+ goto done;
+ }
+ pos += res;
+#ifdef MXIT_DEBUG_EMO
+ purple_debug_info( MXIT_PLUGIN_ID, "read the string '%s'\n", str );
+#endif
+ g_free( str );
+ str = NULL;
+
+ /* utf-8 (emoticon shortcut) */
+ res = asn_getUtf8( &data[pos], 0x81, &str );
+ if ( res <= 0 ) {
+ /* bad utf-8 string */
+ purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad shortcut string)\n" );
+ goto done;
+ }
+ pos += res;
+#ifdef MXIT_DEBUG_EMO
+ purple_debug_info( MXIT_PLUGIN_ID, "read the string '%s'\n", str );
+#endif
+ em_id = str;
+
+ /* validate the image data type */
+ if ( data[pos] != '\x82' ) {
+ /* bad frame desc */
+ purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad data type)\n" );
+ g_free( em_id );
+ goto done;
+ }
+ pos++;
+
+ /* get the data length */
+ res = asn_getlength( &data[pos], &em_size );
+ if ( res <= 0 ) {
+ /* bad frame length */
+ purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad data length)\n" );
+ g_free( em_id );
+ goto done;
+ }
+ pos += res;
+#ifdef MXIT_DEBUG_EMO
+ purple_debug_info( MXIT_PLUGIN_ID, "read the length '%i'\n", em_size );
+#endif
+
+ if ( g_hash_table_lookup( mx->session->iimages, em_id ) ) {
+ /* emoticon found in the table, so ignore this one */
+ goto done;
+ }
+
+ /* make a copy of the data */
+ em_data = g_malloc( em_size );
+ memcpy( em_data, &data[pos], em_size );
+
+ /* strip the mxit markup tags from the emoticon id */
+ if ( ( em_id[0] == '.' ) && ( em_id[1] == '{' ) ) {
+ parse_emoticon_str( &em_id[2], emo );
+ strcpy( em_id, emo );
+ }
+
+ /* we now have the emoticon, store it in the imagestore */
+ id = purple_imgstore_add_with_id( em_data, em_size, NULL );
+
+ /* map the mxit emoticon id to purple image id */
+ intptr = g_malloc( sizeof( int ) );
+ *intptr = id;
+ g_hash_table_insert( mx->session->iimages, em_id, intptr );
+
+ mx->flags |= PURPLE_MESSAGE_IMAGES;
+done:
+ mx->img_count--;
+ if ( ( mx->img_count == 0 ) && ( mx->converted ) ) {
+ /*
+ * this was the last outstanding emoticon for this message,
+ * so we can now display it to the user.
+ */
+ mxit_show_message( mx );
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a request to the MXit WAP site to download the specified emoticon.
+ *
+ * @param mx The Markup message object
+ * @param id The ID for the emoticon
+ */
+static void emoticon_request( struct RXMsgData* mx, const char* id )
+{
+ PurpleUtilFetchUrlData* url_data;
+ const char* wapserver;
+ char* url;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "sending request for emoticon '%s'\n", id );
+
+ wapserver = purple_account_get_string( mx->session->acc, MXIT_CONFIG_WAPSERVER, DEFAULT_WAPSITE );
+
+ /* reference: "libpurple/util.h" */
+ url = g_strdup_printf( "%s/res/?type=emo&mlh=%i&sc=%s&ts=%li", wapserver, MXIT_EMOTICON_SIZE, id, time( NULL ) );
+ url_data = purple_util_fetch_url_request( url, TRUE, NULL, TRUE, NULL, FALSE, emoticon_returned, mx );
+ g_free( url );
+}
+
+
+/*------------------------------------------------------------------------
+ * Parse a Vibe command.
+ *
+ * @param mx The Markup message object
+ * @param message The message text (which contains the vibe)
+ * @return id The length of the message to skip
+ */
+static int mxit_parse_vibe( struct RXMsgData* mx, const char* message )
+{
+ int vibeid;
+
+ vibeid = message[2] - '0';
+
+ purple_debug_info( MXIT_PLUGIN_ID, "Vibe received (%i)\n", vibeid );
+
+ if ( vibeid > ( ARRAY_SIZE( vibes ) - 1 ) ) {
+ purple_debug_warning( MXIT_PLUGIN_ID, "Unsupported vibe received (%i)\n", vibeid );
+ /* unsupported vibe */
+ return 0;
+ }
+
+ g_string_append_printf( mx->msg, "<font color=\"%s\"><i>%s Vibe...</i></font>", MXIT_VIBE_MSG_COLOR, vibes[vibeid] );
+ return 2;
+}
+
+
+/*------------------------------------------------------------------------
+ * Extract the nickname from a chatroom message and display it nicely in
+ * libPurple-style (HTML) markup.
+ *
+ * @param mx The received message data object
+ * @param message The message text
+ * @return The length of the message to skip
+ */
+static int mxit_extract_chatroom_nick( struct RXMsgData* mx, char* message, int len )
+{
+ int i;
+
+ if ( message[0] == '<' ) {
+ /*
+ * The message MIGHT contains an embedded nickname. But we can't
+ * be sure unless we find the end-of-nickname sequence: (>\n)
+ * Search for it....
+ */
+ gboolean found = FALSE;
+ gchar* nickname;
+
+ for ( i = 1; i < len; i++ ) {
+ if ( ( message[i] == '\n' ) && ( message[i-1] == '>' ) ) {
+ found = TRUE;
+ message[i-1] = '\0'; /* loose the '>' */
+ i++; /* and skip the new-line */
+ break;
+ }
+ }
+
+ if ( found ) {
+ /*
+ * The message definitely had an embedded nickname - generate a marked-up
+ * message to be displayed.
+ */
+ nickname = g_markup_escape_text( &message[1], -1 );
+
+ /* add nickname within some BOLD markup to the new converted message */
+ g_string_append_printf( mx->msg, "<b>%s:</b> ", nickname );
+
+ /* free up the resources */
+ g_free( nickname );
+
+ return i;
+ }
+ }
+
+ return 0;
+}
+
+
+
+/*------------------------------------------------------------------------
+ * Convert a message containing MXit protocol markup to libPurple-style (HTML) markup.
+ *
+ * @param mx The received message data object
+ * @param message The message text
+ * @param len The length of the message
+ */
+void mxit_parse_markup( struct RXMsgData* mx, char* message, int len, short msgtype, int msgflags )
+{
+ char tmpstr1[128];
+ char* ch;
+ int i = 0;
+
+ /* tags */
+ gboolean tag_bold = FALSE;
+ gboolean tag_under = FALSE;
+ gboolean tag_italic = FALSE;
+
+#ifdef MXIT_DEBUG_MARKUP
+ purple_debug_info( MXIT_PLUGIN_ID, "Markup RX (original): '%s'\n", message );
+#endif
+
+
+ /*
+ * supported MXit markup:
+ * '*' bold
+ * '_' underline
+ * '/' italics
+ * '$' highlight text
+ * '.+' inc font size
+ * '.-' dec font size
+ * '#XXXXXX' foreground color
+ * '.{XX}' custom emoticon
+ * '\' escape the following character
+ * '::' MXit commands
+ */
+
+
+ if ( is_mxit_chatroom_contact( mx->session, mx->from ) ) {
+ /* chatroom message, so we need to extract and skip the sender's nickname
+ * which is embedded inside the message */
+ i = mxit_extract_chatroom_nick( mx, message, len );
+ }
+
+ /* run through the message and check for custom emoticons and markup */
+ for ( ; i < len; i++ ) {
+ switch ( message[i] ) {
+
+
+ /* mxit markup parsing */
+ case '*' :
+ if ( !( msgflags & CP_MSG_MARKUP ) ) {
+ g_string_append_c( mx->msg, message[i] );
+ break;
+ }
+
+ /* bold markup */
+ if ( !tag_bold )
+ g_string_append( mx->msg, "<b>" );
+ else
+ g_string_append( mx->msg, "</b>" );
+ tag_bold = !tag_bold;
+ break;
+ case '_' :
+ if ( !( msgflags & CP_MSG_MARKUP ) ) {
+ g_string_append_c( mx->msg, message[i] );
+ break;
+ }
+
+ /* underscore markup */
+ if ( !tag_under )
+ g_string_append( mx->msg, "<u>" );
+ else
+ g_string_append( mx->msg, "</u>" );
+ tag_under = !tag_under;
+ break;
+ case '/' :
+ if ( !( msgflags & CP_MSG_MARKUP ) ) {
+ g_string_append_c( mx->msg, message[i] );
+ break;
+ }
+
+ /* italics markup */
+ if ( !tag_italic )
+ g_string_append( mx->msg, "<i>" );
+ else
+ g_string_append( mx->msg, "</i>" );
+ tag_italic = !tag_italic;
+ break;
+ case '$' :
+ if ( !( msgflags & CP_MSG_MARKUP ) ) {
+ g_string_append_c( mx->msg, message[i] );
+ break;
+ }
+ else if ( i + 1 >= len ) {
+ /* message too short for complete link */
+ g_string_append_c( mx->msg, '$' );
+ break;
+ }
+
+ /* find the end tag */
+ ch = strstr( &message[i + 1], "$" );
+ if ( ch ) {
+ /* end found */
+ *ch = '\0';
+ mxit_add_html_link( mx, &message[i + 1], &message[i + 1] );
+ *ch = '$';
+ i += ( ch - &message[i + 1] ) + 1;
+ }
+ else {
+ g_string_append_c( mx->msg, message[i] );
+ }
+ /* highlight text */
+ break;
+ case '#' :
+ if ( !( msgflags & CP_MSG_MARKUP ) ) {
+ g_string_append_c( mx->msg, message[i] );
+ break;
+ }
+ else if ( i + COLORCODE_LEN >= len ) {
+ /* message too short for complete colour code */
+ g_string_append_c( mx->msg, '#' );
+ break;
+ }
+
+ /* foreground (text) color */
+ memcpy( tmpstr1, &message[i + 1], COLORCODE_LEN );
+ tmpstr1[ COLORCODE_LEN ] = '\0'; /* terminate string */
+ if ( strcmp( tmpstr1, "??????" ) == 0 ) {
+ /* need to reset the font */
+ g_string_append( mx->msg, "</font>" );
+ i += COLORCODE_LEN;
+ }
+ else if ( strspn( tmpstr1, "0123456789abcdefABCDEF") == COLORCODE_LEN ) {
+ /* definitely a numeric colour code */
+ g_string_append_printf( mx->msg, "<font color=\"#%s\">", tmpstr1 );
+ i += COLORCODE_LEN;
+ }
+ else {
+ /* not valid colour markup */
+ g_string_append_c( mx->msg, '#' );
+ }
+ break;
+ case '.' :
+ if ( !( msgflags & CP_MSG_EMOTICON ) ) {
+ g_string_append_c( mx->msg, message[i] );
+ break;
+ }
+ else if ( i + 1 >= len ) {
+ /* message too short */
+ g_string_append_c( mx->msg, '.' );
+ break;
+ }
+
+ switch ( message[i+1] ) {
+ case '+' :
+ /* increment text size */
+ g_string_append( mx->msg, "<font size=\"+1\">" );
+ i++;
+ break;
+ case '-' :
+ /* decrement text size */
+ g_string_append( mx->msg, "<font size=\"-1\">" );
+ i++;
+ break;
+ case '{' :
+ /* custom emoticon */
+ if ( i + 2 >= len ) {
+ /* message too short */
+ g_string_append_c( mx->msg, '.' );
+ break;
+ }
+
+ parse_emoticon_str( &message[i+2], tmpstr1 );
+ if ( tmpstr1[0] != '\0' ) {
+ mx->got_img = TRUE;
+
+ if ( g_hash_table_lookup( mx->session->iimages, tmpstr1 ) ) {
+ /* emoticon found in the cache, so we do not have to request it from the WAPsite */
+ }
+ else {
+ /* request emoticon from the WAPsite */
+ mx->img_count++;
+ emoticon_request( mx, tmpstr1 );
+ }
+
+ g_string_append_printf( mx->msg, MXIT_II_TAG"%s>", tmpstr1 );
+ i += strlen( tmpstr1 ) + 2;
+ }
+ else
+ g_string_append_c( mx->msg, '.' );
+
+ break;
+ default :
+ g_string_append_c( mx->msg, '.' );
+ break;
+ }
+ break;
+ case '\\' :
+ if ( i + 1 >= len ) {
+ /* message too short for an escaped character */
+ g_string_append_c( mx->msg, '\\' );
+ }
+ else {
+ /* ignore the next character, because its been escaped */
+ g_string_append_c( mx->msg, message[i + 1] );
+ i++;
+ }
+ break;
+
+
+ /* command parsing */
+ case ':' :
+ if ( i + 1 >= len ) {
+ /* message too short */
+ g_string_append_c( mx->msg, ':' );
+ break;
+ }
+
+ if ( message[i+1] == '@' ) {
+ /* this is a vibe! */
+ int size;
+
+ if ( i + 2 >= len ) {
+ /* message too short */
+ g_string_append_c( mx->msg, message[i] );
+ break;
+ }
+
+ size = mxit_parse_vibe( mx, &message[i] );
+ if ( size == 0 )
+ g_string_append_c( mx->msg, message[i] );
+ else
+ i += size;
+ }
+ else if ( msgtype != CP_MSGTYPE_COMMAND ) {
+ /* this is not a command message */
+ g_string_append_c( mx->msg, message[i] );
+ }
+ else if ( message[i+1] == ':' ) {
+ /* parse out the command */
+ int size;
+
+ size = mxit_parse_command( mx, &message[i] );
+ if ( size == 0 )
+ g_string_append_c( mx->msg, ':' );
+ else
+ i += size;
+ }
+ else {
+ g_string_append_c( mx->msg, ':' );
+ }
+ break;
+
+
+ /* these aren't MXit markup, but are interpreted by libPurple */
+ case '<' :
+ g_string_append( mx->msg, "&lt;" );
+ break;
+ case '>' :
+ g_string_append( mx->msg, "&gt;" );
+ break;
+ case '&' :
+ g_string_append( mx->msg, "&amp;" );
+ break;
+ case '"' :
+ g_string_append( mx->msg, "&quot;" );
+ break;
+
+ default :
+ /* text */
+ g_string_append_c( mx->msg, message[i] );
+ break;
+ }
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Insert an inline image command.
+ *
+ * @param mx The message text as processed so far.
+ * @oaram id The imgstore ID of the inline image.
+ */
+static void inline_image_add( GString* mx, int id )
+{
+ PurpleStoredImage *image;
+ gconstpointer img_data;
+ gsize img_size;
+ gchar* enc;
+
+ image = purple_imgstore_find_by_id( id );
+ if ( image == NULL )
+ return;
+
+ img_data = purple_imgstore_get_data( image );
+ img_size = purple_imgstore_get_size( image );
+
+ enc = purple_base64_encode( img_data, img_size );
+
+ g_string_append( mx, "::op=img|dat=" );
+ g_string_append( mx, enc );
+ g_string_append_c( mx, ':' );
+
+ g_free( enc );
+}
+
+
+/*------------------------------------------------------------------------
+ * Convert libpurple (HTML) markup to MXit protocol markup (for sending to MXit).
+ * Any MXit markup codes in the original message also need to be escaped.
+ *
+ * @param message The message text containing libPurple (HTML) markup
+ * @return The message text containing MXit markup
+ */
+char* mxit_convert_markup_tx( const char* message, int* msgtype )
+{
+ GString* mx;
+ struct tag* tag;
+ GList* entry;
+ GList* tagstack = NULL;
+ char* reply;
+ char color[8];
+ int len = strlen ( message );
+ int i;
+
+#ifdef MXIT_DEBUG_MARKUP
+ purple_debug_info( MXIT_PLUGIN_ID, "Markup TX (original): '%s'\n", message );
+#endif
+
+ /*
+ * libPurple uses the following HTML markup codes:
+ * Bold: <b>...</b>
+ * Italics: <i>...</i>
+ * Underline: <u>...</u>
+ * Strikethrough: <s>...</s> (NO MXIT SUPPORT)
+ * Font size: <font size="">...</font>
+ * Font type: <font face="">...</font> (NO MXIT SUPPORT)
+ * Font colour: <font color=#">...</font>
+ * Links: <a href="">...</a>
+ * Newline: <br>
+ * Inline image: <IMG ID="">
+ * The following characters are also encoded:
+ * &amp; &quot; &lt; &gt;
+ */
+
+ /* new message data */
+ mx = g_string_sized_new( len );
+
+ /* run through the message and check for HTML markup */
+ for ( i = 0; i < len; i++ ) {
+
+ switch ( message[i] ) {
+ case '<' :
+ if ( purple_str_has_prefix( &message[i], "<b>" ) || purple_str_has_prefix( &message[i], "</b>" ) ) {
+ /* bold */
+ g_string_append_c( mx, '*' );
+ }
+ else if ( purple_str_has_prefix( &message[i], "<i>" ) || purple_str_has_prefix( &message[i], "</i>" ) ) {
+ /* italics */
+ g_string_append_c( mx, '/' );
+ }
+ else if ( purple_str_has_prefix( &message[i], "<u>" ) || purple_str_has_prefix( &message[i], "</u>" ) ) {
+ /* underline */
+ g_string_append_c( mx, '_' );
+ }
+ else if ( purple_str_has_prefix( &message[i], "<br>" ) ) {
+ /* newline */
+ g_string_append_c( mx, '\n' );
+ }
+ else if ( purple_str_has_prefix( &message[i], "<font size=" ) ) {
+ /* font size */
+ tag = g_new0( struct tag, 1 );
+ tag->type = MXIT_TAG_SIZE;
+ tagstack = g_list_prepend( tagstack, tag );
+ // TODO: implement size control
+ }
+ else if ( purple_str_has_prefix( &message[i], "<font color=" ) ) {
+ /* font colour */
+ tag = g_new0( struct tag, 1 );
+ tag->type = MXIT_TAG_COLOR;
+ tagstack = g_list_append( tagstack, tag );
+ memset( color, 0x00, sizeof( color ) );
+ memcpy( color, &message[i + 13], 7 );
+ g_string_append( mx, color );
+ }
+ else if ( purple_str_has_prefix( &message[i], "</font>" ) ) {
+ /* end of font tag */
+ entry = g_list_last( tagstack );
+ if ( entry ) {
+ tag = entry->data;
+ if ( tag->type == MXIT_TAG_COLOR ) {
+ /* font color reset */
+ g_string_append( mx, "#??????" );
+ }
+ else if ( tag->type == MXIT_TAG_SIZE ) {
+ /* font size */
+ // TODO: implement size control
+ }
+ tagstack = g_list_remove( tagstack, tag );
+ g_free( tag );
+ }
+ }
+ else if ( purple_str_has_prefix( &message[i], "<IMG ID=" ) ) {
+ /* inline image */
+ int imgid;
+
+ if ( sscanf( &message[i+9], "%i", &imgid ) ) {
+ inline_image_add( mx, imgid );
+ *msgtype = CP_MSGTYPE_COMMAND; /* inline image must be sent as a MXit command */
+ }
+ }
+
+ /* skip to end of tag ('>') */
+ for ( i++; ( i < len ) && ( message[i] != '>' ) ; i++ );
+
+ break;
+
+ case '*' : /* MXit bold */
+ case '_' : /* MXit underline */
+ case '/' : /* MXit italic */
+ case '#' : /* MXit font color */
+ case '$' : /* MXit highlight text */
+ case '\\' : /* MXit escape backslash */
+ g_string_append( mx, "\\" ); /* escape character */
+ g_string_append_c( mx, message[i] ); /* character to escape */
+ break;
+
+ default:
+ g_string_append_c( mx, message[i] );
+ break;
+ }
+ }
+
+ /* unescape HTML entities to their literal characters (reference: "libpurple/utils.h") */
+ reply = purple_unescape_html( mx->str );
+
+ g_string_free( mx, TRUE );
+
+#ifdef MXIT_DEBUG_MARKUP
+ purple_debug_info( MXIT_PLUGIN_ID, "Markup TX (converted): '%s'\n", reply );
+#endif
+
+ return reply;
+}
+
+
+/*------------------------------------------------------------------------
+ * Free an emoticon entry.
+ *
+ * @param key MXit emoticon ID
+ * @param value Imagestore ID for emoticon
+ * @param user_data NULL (unused)
+ * @return TRUE
+ */
+static gboolean emoticon_entry_free( gpointer key, gpointer value, gpointer user_data )
+{
+ int* imgid = value;
+
+ /* key is a string */
+ g_free( key );
+
+ /* value is 'id' in imagestore */
+ purple_imgstore_unref_by_id( *imgid );
+ g_free( value );
+
+ return TRUE;
+}
+
+
+/*------------------------------------------------------------------------
+ * Free all entries in the emoticon cache.
+ *
+ * @param session The MXit session object
+ */
+void mxit_free_emoticon_cache( struct MXitSession* session )
+{
+ g_hash_table_foreach_remove( session->iimages, emoticon_entry_free, NULL );
+ g_hash_table_destroy ( session->iimages );
+}
diff --git a/libpurple/protocols/mxit/markup.h b/libpurple/protocols/mxit/markup.h
new file mode 100644
index 0000000000..bffc5b80d9
--- /dev/null
+++ b/libpurple/protocols/mxit/markup.h
@@ -0,0 +1,40 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- convert between MXit and libPurple markup --
+ *
+ * Pieter Loubser <libpurple@mxit.com>
+ *
+ * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
+ * <http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#ifndef _MXIT_MARKUP_H_
+#define _MXIT_MARKUP_H_
+
+#define MXIT_II_TAG "<MXII=" /* inline image placeholder string */
+
+
+void mxit_parse_markup( struct RXMsgData* mx, char* message, int len, short msgtype, int msgflags );
+char* mxit_convert_markup_tx( const char* message, int* msgtype );
+void mxit_add_html_link( struct RXMsgData* mx, const char* linkname, const char* displayname );
+void mxit_show_message( struct RXMsgData* mx );
+
+void mxit_free_emoticon_cache( struct MXitSession* session );
+
+
+#endif /* _MXIT_MARKUP_H_ */
diff --git a/libpurple/protocols/mxit/multimx.c b/libpurple/protocols/mxit/multimx.c
new file mode 100644
index 0000000000..775af2597c
--- /dev/null
+++ b/libpurple/protocols/mxit/multimx.c
@@ -0,0 +1,597 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- MultiMx GroupChat --
+ *
+ * Andrew Victor <libpurple@mxit.com>
+ *
+ * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
+ * <http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include <string.h>
+#include <errno.h>
+
+#include "purple.h"
+#include "prpl.h"
+
+#include "protocol.h"
+#include "mxit.h"
+#include "multimx.h"
+#include "markup.h"
+
+
+#if 0
+static void multimx_dump(struct multimx* multimx)
+{
+ purple_debug_info(MXIT_PLUGIN_ID, "MultiMX:\n");
+ purple_debug_info(MXIT_PLUGIN_ID, " Chat ID: %i\n", multimx->chatid);
+ purple_debug_info(MXIT_PLUGIN_ID, " Username: %s\n", multimx->roomid);
+ purple_debug_info(MXIT_PLUGIN_ID, " Alias: %s\n", multimx->roomname);
+ purple_debug_info(MXIT_PLUGIN_ID, " State: %i\n", multimx->state);
+}
+#endif
+
+
+/*------------------------------------------------------------------------
+ * Find a MultiMx session based on libpurple chatID.
+ *
+ * @param session The MXit session object
+ * @param id The libpurple group-chat ID
+ * @return The MultiMX room object (or NULL if not found)
+ */
+static struct multimx* find_room_by_id(struct MXitSession* session, int id)
+{
+ GList* x = session->rooms;
+
+ while (x != NULL) {
+ struct multimx* multimx = (struct multimx *) x->data;
+
+ if (multimx->chatid == id)
+ return multimx;
+
+ x = g_list_next(x);
+ }
+
+ return NULL;
+}
+
+
+/*------------------------------------------------------------------------
+ * Find a MultiMx session based on Alias
+ *
+ * @param session The MXit session object
+ * @param roomname The UI room-name
+ * @return The MultiMX room object (or NULL if not found)
+ */
+static struct multimx* find_room_by_alias(struct MXitSession* session, const char* roomname)
+{
+ GList* x = session->rooms;
+
+ while (x != NULL) {
+ struct multimx* multimx = (struct multimx *) x->data;
+
+ if (!strcmp(multimx->roomname, roomname))
+ return multimx;
+
+ x = g_list_next(x);
+ }
+
+ return NULL;
+}
+
+
+/*------------------------------------------------------------------------
+ * Find a MultiMx session based on Username (MXit RoomId)
+ *
+ * @param session The MXit session object
+ * @param username The MXit RoomID (MultiMX contact username)
+ * @return The MultiMX room object (or NULL if not found)
+ */
+static struct multimx* find_room_by_username(struct MXitSession* session, const char* username)
+{
+ GList* x = session->rooms;
+
+ while (x != NULL) {
+ struct multimx* multimx = (struct multimx *) x->data;
+
+ if (!strcmp(multimx->roomid, username))
+ return multimx;
+
+ x = g_list_next(x);
+ }
+
+ return NULL;
+}
+
+
+/*------------------------------------------------------------------------
+ * Create a GroupChat room, and add to list of rooms.
+ *
+ * @param session The MXit session object
+ * @param roomid The MXit RoomID (MultiMX contact username)
+ * @param roomname The UI room-name
+ * @param state The initial state of the room (see multimx.h)
+ * @return The MultiMX room object
+ */
+static struct multimx* room_create(struct MXitSession* session, const char* roomid, const char* roomname, short state)
+{
+ struct multimx* multimx = NULL;
+ static int groupchatID = 1;
+
+ purple_debug_info(MXIT_PLUGIN_ID, "Groupchat create - roomid='%s' roomname='%s'\n", roomid, roomname);
+
+ /* Create a new GroupChat */
+ multimx = g_new0(struct multimx, 1);
+
+ /* Initialize groupchat */
+ g_strlcpy(multimx->roomid, roomid, sizeof(multimx->roomid));
+ g_strlcpy(multimx->roomname, roomname, sizeof(multimx->roomname));
+ multimx->chatid = groupchatID++;
+ multimx->state = state;
+
+ /* Add to GroupChat list */
+ session->rooms = g_list_append(session->rooms, multimx);
+
+ return multimx;
+}
+
+
+/*------------------------------------------------------------------------
+ * Free the Groupchat room.
+ *
+ * @param session The MXit session object
+ * @param multimx The MultiMX room object to deallocate
+ */
+static void room_remove(struct MXitSession* session, struct multimx* multimx)
+{
+ /* Remove from GroupChat list */
+ session->rooms = g_list_remove(session->rooms, multimx);
+
+ /* Deallocate it */
+ free (multimx);
+ multimx = NULL;
+}
+
+
+/*------------------------------------------------------------------------
+ * Another user has join the GroupChat, add them to the member-list.
+ *
+ * @param session The MXit session object
+ * @param multimx The MultiMX room object
+ * @param nickname The nickname of the user who joined the room
+ */
+static void member_added(struct MXitSession* session, struct multimx* multimx, const char* nickname)
+{
+ PurpleConversation *convo;
+
+ purple_debug_info(MXIT_PLUGIN_ID, "member_added: '%s'\n", nickname);
+
+ convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, multimx->roomname, session->acc);
+ if (convo == NULL) {
+ purple_debug_error(MXIT_PLUGIN_ID, "Conversation '%s' not found\n", multimx->roomname);
+ return;
+ }
+
+ purple_conv_chat_add_user(PURPLE_CONV_CHAT(convo), nickname, NULL, PURPLE_CBFLAGS_NONE, TRUE);
+}
+
+
+/*------------------------------------------------------------------------
+ * Another user has left the GroupChat, remove them from the member-list.
+ *
+ * @param session The MXit session object
+ * @param multimx The MultiMX room object
+ * @param nickname The nickname of the user who left the room
+ */
+static void member_removed(struct MXitSession* session, struct multimx* multimx, const char* nickname)
+{
+ PurpleConversation *convo;
+
+ purple_debug_info(MXIT_PLUGIN_ID, "member_removed: '%s'\n", nickname);
+
+ convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, multimx->roomname, session->acc);
+ if (convo == NULL) {
+ purple_debug_error(MXIT_PLUGIN_ID, "Conversation '%s' not found\n", multimx->roomname);
+ return;
+ }
+
+ purple_conv_chat_remove_user(PURPLE_CONV_CHAT(convo), nickname, NULL);
+}
+
+
+/*------------------------------------------------------------------------
+ * Update the full GroupChat member list.
+ *
+ * @param session The MXit session object
+ * @param multimx The MultiMX room object
+ * @param data The nicknames of the users in the room (separated by \n)
+ */
+static void member_update(struct MXitSession* session, struct multimx* multimx, char* data)
+{
+ PurpleConversation *convo;
+ gchar** userlist;
+ int i = 0;
+
+ purple_debug_info(MXIT_PLUGIN_ID, "member_update: '%s'\n", data);
+
+ convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, multimx->roomname, session->acc);
+ if (convo == NULL) {
+ purple_debug_error(MXIT_PLUGIN_ID, "Conversation '%s' not found\n", multimx->roomname);
+ return;
+ }
+
+ /* Clear list */
+ purple_conv_chat_clear_users(PURPLE_CONV_CHAT(convo));
+
+ /* Add each member */
+ data = g_strstrip(data); /* string leading & trailing whitespace */
+ userlist = g_strsplit(data, "\n", 0); /* tokenize string */
+ while (userlist[i] != NULL) {
+ purple_debug_info(MXIT_PLUGIN_ID, "member_update - adding: '%s'\n", userlist[i]);
+ purple_conv_chat_add_user(PURPLE_CONV_CHAT(convo), userlist[i], NULL, PURPLE_CBFLAGS_NONE, FALSE);
+ i++;
+ }
+ g_strfreev(userlist);
+}
+
+
+/* -------------------------------------------------------------------------------------------------
+ * Calls from MXit Protocol layer
+ * ------------------------------------------------------------------------------------------------- */
+
+/*------------------------------------------------------------------------
+ * Received a Subscription Request to a MultiMX room.
+ *
+ * @param session The MXit session object
+ * @param contact The invited MultiMX room's contact information
+ * @param creator The nickname of the room's creator / invitor
+ */
+void multimx_invite(struct MXitSession* session, struct contact* contact, const char* creator)
+{
+ GHashTable *components;
+ struct multimx* multimx = NULL;
+
+ purple_debug_info(MXIT_PLUGIN_ID, "Groupchat invite to '%s' by '%s'\n", contact->alias, creator);
+
+ /* Create a new room */
+ multimx = room_create(session, contact->username, contact->alias, STATE_INVITED);
+
+ components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+ g_hash_table_insert(components, g_strdup("room"), g_strdup(contact->alias));
+
+ /* Call libpurple - will trigger either 'mxit_chat_join' or 'mxit_chat_reject' */
+ serv_got_chat_invite(session->con, contact->alias, creator, NULL, components);
+}
+
+
+/*------------------------------------------------------------------------
+ * MultiMX room has been added to the roster.
+ *
+ * @param session The MXit session object
+ * @param contact The MultiMX room's contact information
+ */
+void multimx_created(struct MXitSession* session, struct contact* contact)
+{
+ PurpleConnection *gc = session->con;
+ struct multimx* multimx = NULL;
+
+ purple_debug_info(MXIT_PLUGIN_ID, "Groupchat '%s' created as '%s'\n", contact->alias, contact->username);
+
+ /* Find matching MultiMX group */
+ multimx = find_room_by_username(session, contact->username);
+ if (multimx == NULL) {
+ multimx = room_create(session, contact->username, contact->alias, TRUE);
+ }
+ else if (multimx->state == STATE_INVITED) {
+ /* After successfully accepting an invitation */
+ multimx->state = STATE_JOINED;
+ }
+
+ /* Call libpurple - will trigger 'mxit_chat_join' */
+ serv_got_joined_chat(gc, multimx->chatid, multimx->roomname);
+
+ /* Send ".list" command to GroupChat server to retrieve current member-list */
+ mxit_send_message(session, multimx->roomid, ".list", FALSE);
+}
+
+
+/*------------------------------------------------------------------------
+ * Is this username a MultiMX contact?
+ *
+ * @param session The MXit session object
+ * @param username The username of the contact
+ * @return TRUE if this contacts matches the RoomID of a MultiMX room.
+ */
+gboolean is_multimx_contact(struct MXitSession* session, const char* username)
+{
+ /* Check for username in list of open rooms */
+ return (find_room_by_username(session, username) != NULL);
+}
+
+
+/*------------------------------------------------------------------------
+ * Received a message from a MultiMX room.
+ *
+ */
+void multimx_message_received(struct RXMsgData* mx, char* msg, int msglen, short msgtype, int msgflags)
+{
+ struct multimx* multimx = NULL;
+
+ purple_debug_info(MXIT_PLUGIN_ID, "Groupchat message received: %s\n", msg);
+
+ /* Find matching multimx group */
+ multimx = find_room_by_username(mx->session, mx->from);
+ if (multimx == NULL) {
+ purple_debug_error(MXIT_PLUGIN_ID, "Groupchat '%s' not found\n", mx->from);
+ return;
+ }
+
+ /* Determine if system message or a message from a contact */
+ if (msg[0] == '<') {
+ /* Message contains embedded nickname - must be from contact */
+ unsigned int i;
+
+ for (i = 1; i < strlen(msg); i++) { /* search for end of nickname */
+ if (msg[i] == '>') {
+ msg[i] = '\0';
+ g_free(mx->from);
+ mx->from = g_strdup(&msg[1]);
+ msg = &msg[i+2]; /* skip '>' and newline */
+ break;
+ }
+ }
+
+ /* now do markup processing on the message */
+ mx->chatid = multimx->chatid;
+ mxit_parse_markup(mx, msg, strlen(msg), msgtype, msgflags);
+ }
+ else {
+ /* Must be a service message */
+ char* ofs;
+
+ /* Determine if somebody has joined or left - update member-list */
+ if ((ofs = strstr(msg, " has joined")) != NULL) {
+ /* Somebody has joined */
+ *ofs = '\0';
+ member_added(mx->session, multimx, msg);
+ mx->processed = TRUE;
+ }
+ else if ((ofs = strstr(msg, " has left")) != NULL) {
+ /* Somebody has left */
+ *ofs = '\0';
+ member_removed(mx->session, multimx, msg);
+ mx->processed = TRUE;
+ }
+ else if (g_str_has_prefix(msg, "The following users are in this MultiMx:") == TRUE) {
+ member_update(mx->session, multimx, msg + strlen("The following users are in this MultiMx:") + 1);
+ mx->processed = TRUE;
+ }
+ else {
+ /* Display server message in chat window */
+ serv_got_chat_in(mx->session->con, multimx->chatid, "MXit", PURPLE_MESSAGE_SYSTEM, msg, mx->timestamp);
+ mx->processed = TRUE;
+ }
+ }
+}
+
+
+
+/* -------------------------------------------------------------------------------------------------
+ * Callbacks from libpurple
+ * ------------------------------------------------------------------------------------------------- */
+
+/*------------------------------------------------------------------------
+ * User has selected "Add Chat" from the main menu.
+ *
+ * @param gc The connection object
+ * @return A list of chat configuration values
+ */
+GList* mxit_chat_info(PurpleConnection *gc)
+{
+ GList *m = NULL;
+ struct proto_chat_entry *pce;
+
+ /* Configuration option: Room Name */
+ pce = g_new0(struct proto_chat_entry, 1);
+ pce->label = "_Room Name:";
+ pce->identifier = "room";
+ pce->required = TRUE;
+ m = g_list_append(m, pce);
+
+ return m;
+}
+
+
+/*------------------------------------------------------------------------
+ * User has joined a chatroom, either because they are creating it or they
+ * accepted an invite.
+ *
+ * @param gc The connection object
+ * @param components The list of chat configuration values
+ */
+void mxit_chat_join(PurpleConnection *gc, GHashTable *components)
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+ const char* roomname = NULL;
+ struct multimx* multimx = NULL;
+
+ purple_debug_info(MXIT_PLUGIN_ID, "mxit_chat_join\n");
+
+ /* Determine if groupchat already exists */
+ roomname = g_hash_table_lookup(components, "room");
+ multimx = find_room_by_alias(session, roomname);
+
+ if (multimx != NULL) {
+ /* The room information already exists */
+
+ if (multimx->state == STATE_INVITED) {
+ /* Invite is pending */
+ purple_debug_info(MXIT_PLUGIN_ID, "Groupchat %i accept sent\n", multimx->chatid);
+
+ /* Send Subscription Accept to MXit */
+ mxit_send_allow_sub(session, multimx->roomid, multimx->roomname);
+ }
+ else {
+ /* Join existing room */
+ purple_debug_info(MXIT_PLUGIN_ID, "Groupchat %i rejoined\n", multimx->chatid);
+
+ serv_got_joined_chat(gc, multimx->chatid, multimx->roomname);
+ }
+ }
+ else {
+ /* Send Groupchat Create to MXit */
+ mxit_send_groupchat_create(session, roomname, 0, NULL);
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * User has rejected an invite to join a MultiMX room.
+ *
+ * @param gc The connection object
+ * @param components The list of chat configuration values
+ */
+void mxit_chat_reject(PurpleConnection *gc, GHashTable* components)
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+ const char* roomname = NULL;
+ struct multimx* multimx = NULL;
+
+ purple_debug_info(MXIT_PLUGIN_ID, "mxit_chat_reject\n");
+
+ roomname = g_hash_table_lookup(components, "room");
+ multimx = find_room_by_alias(session, roomname);
+ if (multimx == NULL) {
+ purple_debug_error(MXIT_PLUGIN_ID, "Groupchat '%s' not found\n", roomname);
+ return;
+ }
+
+ /* Send Subscription Reject to MXit */
+ mxit_send_deny_sub(session, multimx->roomid);
+
+ /* Remove from our list of rooms */
+ room_remove(session, multimx);
+}
+
+
+/*------------------------------------------------------------------------
+ * Return name of chatroom (on mouse hover)
+ *
+ * @param components The list of chat configuration values.
+ * @return The name of the chat room
+ */
+char* mxit_chat_name(GHashTable *components)
+{
+ return g_strdup(g_hash_table_lookup(components, "room"));
+}
+
+
+/*------------------------------------------------------------------------
+ * User has selected to invite somebody to a chatroom.
+ *
+ * @param gc The connection object
+ * @param id The chat room ID
+ * @param msg The invitation message entered by the user
+ * @param name The username of the person to invite
+ */
+void mxit_chat_invite(PurpleConnection *gc, int id, const char *msg, const char *username)
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+ struct multimx* multimx = NULL;
+
+ purple_debug_info(MXIT_PLUGIN_ID, "Groupchat invite to '%s'\n", username);
+
+ /* Find matching MultiMX group */
+ multimx = find_room_by_id(session, id);
+ if (multimx == NULL) {
+ purple_debug_error(MXIT_PLUGIN_ID, "Could not find groupchat %i\n", id);
+ return;
+ }
+
+ /* Send invite to MXit */
+ mxit_send_groupchat_invite(session, multimx->roomid, 1, &username);
+}
+
+
+/*------------------------------------------------------------------------
+ * User as closed the chat window, and the chatroom is not marked as persistent.
+ *
+ * @param gc The connection object
+ * @param id The chat room ID
+ */
+void mxit_chat_leave(PurpleConnection *gc, int id)
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+ struct multimx* multimx = NULL;
+
+ purple_debug_info(MXIT_PLUGIN_ID, "Groupchat %i leave\n", id);
+
+ /* Find matching multimx group */
+ multimx = find_room_by_id(session, id);
+ if (multimx == NULL) {
+ purple_debug_error(MXIT_PLUGIN_ID, "Could not find groupchat %i\n", id);
+ return;
+ }
+
+ /* Send Remove Groupchat to MXit */
+ mxit_send_remove(session, multimx->roomid);
+
+ /* Remove from our list of rooms */
+ room_remove(session, multimx);
+}
+
+
+/*------------------------------------------------------------------------
+ * User has entered a message in a chatroom window, send it to the MXit server.
+ *
+ * @param gc The connection object
+ * @param id The chat room ID
+ * @param message The sent message data
+ * @param flags The message flags
+ * @return Indicates success / failure
+ */
+int mxit_chat_send(PurpleConnection *gc, int id, const char *message, PurpleMessageFlags flags)
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+ struct multimx* multimx = NULL;
+ const char* nickname;
+
+ purple_debug_info(MXIT_PLUGIN_ID, "Groupchat %i message send: '%s'\n", id, message);
+
+ /* Find matching MultiMX group */
+ multimx = find_room_by_id(session, id);
+ if (multimx == NULL) {
+ purple_debug_error(MXIT_PLUGIN_ID, "Could not find groupchat %i\n", id);
+ return -1;
+ }
+
+ /* Send packet to MXit */
+ mxit_send_message(session, multimx->roomid, message, TRUE);
+
+ /* Determine our nickname to display */
+ if (session->profile && (session->profile->nickname[0] != '\0')) /* default is profile name (since that's what everybody else sees) */
+ nickname = session->profile->nickname;
+ else
+ nickname = purple_account_get_alias(purple_connection_get_account(gc)); /* local alias */
+
+ /* Display message in chat window */
+ serv_got_chat_in(gc, id, nickname, flags, message, time(NULL));
+
+ return 0;
+}
+
diff --git a/libpurple/protocols/mxit/multimx.h b/libpurple/protocols/mxit/multimx.h
new file mode 100644
index 0000000000..868f91b216
--- /dev/null
+++ b/libpurple/protocols/mxit/multimx.h
@@ -0,0 +1,104 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- MultiMx GroupChat --
+ *
+ * Andrew Victor <libpurple@mxit.com>
+ *
+ * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
+ * <http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#ifndef _MXIT_MULTIMX_H_
+#define _MXIT_MULTIMX_H_
+
+#include "roster.h"
+
+
+/* GroupChat Room state */
+#define STATE_CREATOR 0
+#define STATE_INVITED 1
+#define STATE_JOINED 2
+
+/*
+ * a MultiMX room
+ */
+struct multimx {
+ char roomname[MXIT_CP_MAX_ALIAS_LEN]; /* name of the room */
+ char roomid[MXIT_CP_MAX_JID_LEN]; /* internal JID for room */
+ int chatid; /* libpurple chat ID */
+ short state; /* state */
+};
+
+
+/*
+ * Received a Subscription Request to a MultiMX room.
+ */
+void multimx_invite(struct MXitSession* session, struct contact* contact, const char* creator);
+
+/*
+ * MultiMX room has been added to the roster.
+ */
+void multimx_created(struct MXitSession* session, struct contact* contact);
+
+/*
+ * Is this username a MultiMX contact?
+ */
+gboolean is_multimx_contact(struct MXitSession* session, const char* username);
+
+/*
+ * Received a message from a MultiMX room.
+ */
+void multimx_message_received(struct RXMsgData* mx, char* message, int len, short msgtype, int msgflags);
+
+/*
+ * User has selected "Add Chat" from the main menu.
+ */
+GList* mxit_chat_info(PurpleConnection *gc);
+
+/*
+ * User has joined a chatroom, either because they are creating it or they accepted an invite.
+ */
+void mxit_chat_join(PurpleConnection *gc, GHashTable *data);
+
+/*
+ * User has rejected an invite to join a MultiMX room.
+ */
+void mxit_chat_reject(PurpleConnection *gc, GHashTable* components);
+
+/*
+ * Return name of chatroom (on mouse hover)
+ */
+char* mxit_chat_name(GHashTable *data);
+
+/*
+ * User has selected to invite somebody to a chatroom.
+ */
+void mxit_chat_invite(PurpleConnection *gc, int id, const char *msg, const char *name);
+
+/*
+ * User as closed the chat window, and the chatroom is not marked as persistent.
+ */
+void mxit_chat_leave(PurpleConnection *gc, int id);
+
+/*
+ * User has entered a message in a chatroom window, send it to the MXit server.
+ */
+int mxit_chat_send(PurpleConnection *gc, int id, const char *message, PurpleMessageFlags flags);
+
+
+#endif /* _MXIT_MULTIMX_H_ */
diff --git a/libpurple/protocols/mxit/mxit.c b/libpurple/protocols/mxit/mxit.c
new file mode 100644
index 0000000000..43d8a7a0c0
--- /dev/null
+++ b/libpurple/protocols/mxit/mxit.c
@@ -0,0 +1,694 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- MXit libPurple plugin API --
+ *
+ * Pieter Loubser <libpurple@mxit.com>
+ *
+ * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
+ * <http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include <glib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "purple.h"
+#include "notify.h"
+#include "plugin.h"
+#include "version.h"
+
+#include "mxit.h"
+#include "protocol.h"
+#include "login.h"
+#include "roster.h"
+#include "chunk.h"
+#include "filexfer.h"
+#include "actions.h"
+#include "multimx.h"
+
+
+#ifdef MXIT_LINK_CLICK
+
+
+/* pidgin callback function pointers for URI click interception */
+static void *(*mxit_pidgin_uri_cb)(const char *uri);
+static PurpleNotifyUiOps* mxit_nots_override_original;
+static PurpleNotifyUiOps mxit_nots_override;
+static int not_link_ref_count = 0;
+
+
+/*------------------------------------------------------------------------
+ * Handle an URI clicked on the UI
+ *
+ * @param link the link name which has been clicked
+ */
+static void* mxit_link_click( const char* link64 )
+{
+ PurpleAccount* account;
+ PurpleConnection* con;
+ gchar** parts = NULL;
+ gchar* link = NULL;
+ unsigned int len;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_link_click (%s)\n", link64 );
+
+ if ( g_ascii_strncasecmp( link64, MXIT_LINK_PREFIX, strlen( MXIT_LINK_PREFIX ) ) != 0 ) {
+ /* this is not for us */
+ goto skip;
+ }
+
+ /* decode the base64 payload */
+ link = (gchar*) purple_base64_decode( link64 + strlen( MXIT_LINK_PREFIX ), &len );
+ purple_debug_info( MXIT_PLUGIN_ID, "Clicked Link: '%s'\n", link );
+
+ parts = g_strsplit( link, "|", 5 );
+
+ /* check if this is a valid mxit link */
+ if ( ( !parts ) || ( !parts[0] ) || ( !parts[1] ) || ( !parts[2] ) || ( !parts[3] ) || ( !parts[4] ) ) {
+ /* this is not for us */
+ goto skip;
+ }
+ else if ( g_ascii_strcasecmp( parts[0], MXIT_LINK_KEY ) != 0 ) {
+ /* this is not for us */
+ goto skip;
+ }
+
+ /* find the account */
+ account = purple_accounts_find( parts[1], parts[2] );
+ if ( !account )
+ goto skip;
+ con = purple_account_get_connection( account );
+
+ /* send click message back to MXit */
+ mxit_send_message( con->proto_data, parts[3], parts[4], FALSE );
+
+ g_free( link );
+ link = NULL;
+ g_strfreev( parts );
+ parts = NULL;
+
+ return (void*) link64;
+
+skip:
+ /* this is not an internal mxit link */
+
+ if ( link )
+ g_free( link );
+ link = NULL;
+
+ if ( parts )
+ g_strfreev( parts );
+ parts = NULL;
+
+ if ( mxit_pidgin_uri_cb )
+ return mxit_pidgin_uri_cb( link64 );
+ else
+ return (void*) link64;
+}
+
+
+/*------------------------------------------------------------------------
+ * Register MXit to receive URI click notifications from the UI
+ */
+void mxit_register_uri_handler()
+{
+ not_link_ref_count++;
+ if ( not_link_ref_count == 1 ) {
+ /* make copy of notifications */
+ mxit_nots_override_original = purple_notify_get_ui_ops();
+ memcpy( &mxit_nots_override, mxit_nots_override_original, sizeof( PurpleNotifyUiOps ) );
+
+ /* save previously configured callback function pointer */
+ mxit_pidgin_uri_cb = mxit_nots_override.notify_uri;
+
+ /* override the URI function call with MXit's own one */
+ mxit_nots_override.notify_uri = mxit_link_click;
+ purple_notify_set_ui_ops( &mxit_nots_override );
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Unegister MXit from receiving URI click notifications from the UI
+ */
+static void mxit_unregister_uri_handler()
+{
+ not_link_ref_count--;
+ if ( not_link_ref_count == 0 ) {
+ /* restore the notifications to its original state */
+ purple_notify_set_ui_ops( mxit_nots_override_original );
+ }
+}
+
+#endif
+
+
+/*------------------------------------------------------------------------
+ * This gets called when a new chat conversation is opened by the user
+ *
+ * @param conv The conversation object
+ * @param session The MXit session object
+ */
+static void mxit_cb_chat_created( PurpleConversation* conv, struct MXitSession* session )
+{
+ PurpleConnection* gc;
+ struct contact* contact;
+ PurpleBuddy* buddy;
+ const char* who;
+
+ gc = purple_conversation_get_gc( conv );
+ if ( session->con != gc ) {
+ /* not our conversation */
+ return;
+ }
+ else if ( purple_conversation_get_type( conv ) != PURPLE_CONV_TYPE_IM ) {
+ /* wrong type of conversation */
+ return;
+ }
+
+ /* get the contact name */
+ who = purple_conversation_get_name( conv );
+ if ( !who )
+ return;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "Conversation started with '%s'\n", who );
+
+ /* find the buddy object */
+ buddy = purple_find_buddy( session->acc, who );
+ if ( ( !buddy ) || ( !buddy->proto_data ) )
+ return;
+
+ /* we ignore all conversations with which we have chatted with in this session */
+ if ( find_active_chat( session->active_chats, who ) )
+ return;
+
+ /* determite if this buddy is a MXit service */
+ contact = buddy->proto_data;
+ switch ( contact->type ) {
+ case MXIT_TYPE_BOT :
+ case MXIT_TYPE_CHATROOM :
+ case MXIT_TYPE_GALLERY :
+ case MXIT_TYPE_INFO :
+ serv_got_im( session->con, who, "<font color=\"#999999\">Loading menu...</font>\n", PURPLE_MESSAGE_NOTIFY, time( NULL ) );
+ mxit_send_message( session, who, " ", FALSE );
+ default :
+ break;
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Enable some signals to handled by our plugin
+ *
+ * @param session The MXit session object
+ */
+void mxit_enable_signals( struct MXitSession* session )
+{
+ /* enable the signal when a new conversation is opened by the user */
+ purple_signal_connect_priority( purple_conversations_get_handle(), "conversation-created", session, PURPLE_CALLBACK( mxit_cb_chat_created ),
+ session, PURPLE_SIGNAL_PRIORITY_HIGHEST );
+}
+
+
+/*------------------------------------------------------------------------
+ * Disable some signals handled by our plugin
+ *
+ * @param session The MXit session object
+ */
+static void mxit_disable_signals( struct MXitSession* session )
+{
+ /* disable the signal when a new conversation is opened by the user */
+ purple_signal_disconnect( purple_conversations_get_handle(), "conversation-created", session, PURPLE_CALLBACK( mxit_cb_chat_created ) );
+}
+
+
+/*------------------------------------------------------------------------
+ * Return the base icon name.
+ *
+ * @param account The MXit account object
+ * @param buddy The buddy
+ * @return The icon name (excluding extension)
+ */
+static const char* mxit_list_icon( PurpleAccount* account, PurpleBuddy* buddy )
+{
+ return "mxit";
+}
+
+
+/*------------------------------------------------------------------------
+ * Return the emblem icon name.
+ *
+ * @param buddy The buddy
+ * @return The icon name (excluding extension)
+ */
+static const char* mxit_list_emblem( PurpleBuddy* buddy )
+{
+ struct contact* contact = buddy->proto_data;
+
+ if ( !contact )
+ return NULL;
+
+ switch ( contact-> type ) {
+ case MXIT_TYPE_JABBER : /* external contacts via MXit */
+ case MXIT_TYPE_MSN :
+ case MXIT_TYPE_YAHOO :
+ case MXIT_TYPE_ICQ :
+ case MXIT_TYPE_AIM :
+ case MXIT_TYPE_QQ :
+ case MXIT_TYPE_WV :
+ return "external";
+
+ case MXIT_TYPE_BOT : /* MXit services */
+ case MXIT_TYPE_GALLERY :
+ case MXIT_TYPE_INFO :
+ return "bot";
+
+ case MXIT_TYPE_CHATROOM : /* MXit group chat services */
+ case MXIT_TYPE_MULTIMX :
+ default:
+ return NULL;
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Return short string representing buddy's status for display on buddy list.
+ * Returns status message (if one is set), or otherwise the mood.
+ *
+ * @param buddy The buddy.
+ * @return The status text
+ */
+char* mxit_status_text( PurpleBuddy* buddy )
+{
+ struct contact* contact = buddy->proto_data;
+
+ if ( !contact )
+ return NULL;
+
+ if ( contact->statusMsg ) {
+ /* status message */
+ return g_strdup( contact-> statusMsg );
+ }
+ else {
+ /* mood */
+ return g_strdup( mxit_convert_mood_to_name( contact->mood ) );
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Return UI tooltip information for a buddy when hovering in buddy list.
+ *
+ * @param buddy The buddy
+ * @param info The tooltip info being returned
+ * @param full Return full or summarized information
+ */
+static void mxit_tooltip( PurpleBuddy* buddy, PurpleNotifyUserInfo* info, gboolean full )
+{
+ struct contact* contact = buddy->proto_data;
+
+ if ( !contact )
+ return;
+
+ /* status (reference: "libpurple/notify.h") */
+ if ( contact->presence != MXIT_PRESENCE_OFFLINE )
+ purple_notify_user_info_add_pair( info, _( "Status" ), mxit_convert_presence_to_name( contact->presence ) );
+
+ /* status message */
+ if ( contact->statusMsg )
+ purple_notify_user_info_add_pair( info, _( "Status Message" ), contact->statusMsg );
+
+ /* mood */
+ if ( contact->mood != MXIT_MOOD_NONE )
+ purple_notify_user_info_add_pair( info, _( "Mood" ), mxit_convert_mood_to_name( contact->mood ) );
+
+ /* subscription type */
+ if ( contact->subtype != 0 )
+ purple_notify_user_info_add_pair( info, _( "Subscription" ), mxit_convert_subtype_to_name( contact->subtype ) );
+
+ /* hidden number */
+ if ( contact->flags & MXIT_CFLAG_HIDDEN )
+ purple_notify_user_info_add_pair( info, _( "Hidden Number" ), "Yes" );
+}
+
+
+/*------------------------------------------------------------------------
+ * Initiate the logout sequence, close the connection and clear the session data.
+ *
+ * @param gc The connection object
+ */
+static void mxit_close( PurpleConnection* gc )
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+
+ /* disable signals */
+ mxit_disable_signals( session );
+
+ /* close the connection */
+ mxit_close_connection( session );
+
+#ifdef MXIT_LINK_CLICK
+ /* unregister for uri click notification */
+ mxit_unregister_uri_handler();
+#endif
+
+ purple_debug_info( MXIT_PLUGIN_ID, "Releasing the session object..\n" );
+
+ /* free the session memory */
+ g_free( session );
+ session = NULL;
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a message to a contact
+ *
+ * @param gc The connection object
+ * @param who The username of the recipient
+ * @param message The message text
+ * @param flags Message flags (defined in conversation.h)
+ * @return Positive value (success, and echo to conversation window)
+ Zero (success, no echo)
+ Negative value (error)
+ */
+static int mxit_send_im( PurpleConnection* gc, const char* who, const char* message, PurpleMessageFlags flags )
+{
+ purple_debug_info( MXIT_PLUGIN_ID, "Sending message '%s' to buddy '%s'\n", message, who );
+
+ mxit_send_message( gc->proto_data, who, message, TRUE );
+
+ return 1; /* echo to conversation window */
+}
+
+
+/*------------------------------------------------------------------------
+ * The user changed their current presence state.
+ *
+ * @param account The MXit account object
+ * @param status The new status (libPurple status type)
+ */
+static void mxit_set_status( PurpleAccount* account, PurpleStatus* status )
+{
+ struct MXitSession* session = purple_account_get_connection( account )->proto_data;
+ const char* statusid;
+ int presence;
+ char* statusmsg1;
+ char* statusmsg2;
+
+ /* get the status id (reference: "libpurple/status.h") */
+ statusid = purple_status_get_id( status );
+
+ /* convert the purple status to a mxit status */
+ presence = mxit_convert_presence( statusid );
+ if ( presence < 0 ) {
+ /* error, status not found */
+ purple_debug_info( MXIT_PLUGIN_ID, "Presence status NOT found! (id = %s)\n", statusid );
+ return;
+ }
+
+ statusmsg1 = purple_markup_strip_html( purple_status_get_attr_string( status, "message" ) );
+ statusmsg2 = g_strndup( statusmsg1, CP_MAX_STATUS_MSG );
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_set_status: '%s'\n", statusmsg2 );
+
+ /* update presence state */
+ mxit_send_presence( session, presence, statusmsg2 );
+
+ g_free( statusmsg1 );
+ g_free( statusmsg2 );
+}
+
+
+/*------------------------------------------------------------------------
+ * MXit supports messages to offline contacts.
+ *
+ * @param buddy The buddy
+ */
+static gboolean mxit_offline_message( const PurpleBuddy *buddy )
+{
+ return TRUE;
+}
+
+
+/*------------------------------------------------------------------------
+ * Free the resources used to store a buddy.
+ *
+ * @param buddy The buddy
+ */
+static void mxit_free_buddy( PurpleBuddy* buddy )
+{
+ struct contact* contact;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_free_buddy\n" );
+
+ contact = buddy->proto_data;
+ if ( contact ) {
+ if ( contact->statusMsg )
+ g_free( contact->statusMsg );
+ if ( contact->avatarId )
+ g_free( contact->avatarId );
+ g_free( contact );
+ }
+ buddy->proto_data = NULL;
+}
+
+
+/*------------------------------------------------------------------------
+ * Periodic task called every KEEPALIVE_INTERVAL (30 sec) to to maintain
+ * idle connections, timeouts and the transmission queue to the MXit server.
+ *
+ * @param gc The connection object
+ */
+static void mxit_keepalive( PurpleConnection *gc )
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+
+ /* if not logged in, there is nothing to do */
+ if ( !( session->flags & MXIT_FLAG_LOGGEDIN ) )
+ return;
+
+ /* pinging is only for socket connections (HTTP does polling) */
+ if ( session->http )
+ return;
+
+ if ( session->last_tx <= time( NULL ) - MXIT_PING_INTERVAL ) {
+ /*
+ * this connection has been idle for too long, better ping
+ * the server before it kills our connection.
+ */
+ mxit_send_ping( session );
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Set or clear our Buddy icon.
+ *
+ * @param gc The connection object
+ * @param img The buddy icon data
+ */
+static void mxit_set_buddy_icon( PurpleConnection *gc, PurpleStoredImage *img )
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+
+ if ( img == NULL )
+ mxit_set_avatar( session, NULL, 0 );
+ else
+ mxit_set_avatar( session, purple_imgstore_get_data( img ), purple_imgstore_get_size( img ) );
+}
+
+
+/*------------------------------------------------------------------------
+ * Request profile information for another MXit contact.
+ *
+ * @param gc The connection object
+ * @param who The username of the contact.
+ */
+static void mxit_get_info( PurpleConnection *gc, const char *who )
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+ const char* profilelist[] = { CP_PROFILE_BIRTHDATE, CP_PROFILE_GENDER, CP_PROFILE_HIDENUMBER, CP_PROFILE_FULLNAME,
+ CP_PROFILE_TITLE, CP_PROFILE_FIRSTNAME, CP_PROFILE_LASTNAME, CP_PROFILE_EMAIL };
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_get_info: '%s'\n", who );
+
+
+ /* send profile request */
+ mxit_send_extprofile_request( session, who, ARRAY_SIZE( profilelist ), profilelist );
+}
+
+
+/*------------------------------------------------------------------------
+ * Return a list of labels to be used by Pidgin for assisting the user.
+ */
+static GHashTable* mxit_get_text_table( PurpleAccount* acc )
+{
+ GHashTable* table;
+
+ table = g_hash_table_new( g_str_hash, g_str_equal );
+
+ g_hash_table_insert( table, "login_label", _( "Your Mobile Number..." ) );
+
+ return table;
+}
+
+/*========================================================================================================================*/
+
+static PurplePluginProtocolInfo proto_info = {
+ OPT_PROTO_REGISTER_NOSCREENNAME | OPT_PROTO_UNIQUE_CHATNAME | OPT_PROTO_IM_IMAGE, /* options */
+ NULL, /* user_splits */
+ NULL, /* protocol_options */
+ { /* icon_spec */
+ "png", /* format */
+ 32, 32, /* min width & height */
+ MXIT_AVATAR_SIZE, /* max width */
+ MXIT_AVATAR_SIZE, /* max height */
+ 100000, /* max filezize */
+ PURPLE_ICON_SCALE_SEND | PURPLE_ICON_SCALE_DISPLAY /* scaling rules */
+ },
+ mxit_list_icon, /* list_icon */
+ mxit_list_emblem, /* list_emblem */
+ mxit_status_text, /* status_text */
+ mxit_tooltip, /* tooltip_text */
+ mxit_status_types, /* status types [roster.c] */
+ NULL, /* blist_node_menu */
+ mxit_chat_info, /* chat_info [multimx.c] */
+ NULL, /* chat_info_defaults */
+ mxit_login, /* login [login.c] */
+ mxit_close, /* close */
+ mxit_send_im, /* send_im */
+ NULL, /* set_info */
+ NULL, /* send_typing */
+ mxit_get_info, /* get_info */
+ mxit_set_status, /* set_status */
+ NULL, /* set_idle */
+ NULL, /* change_passwd */
+ mxit_add_buddy, /* add_buddy [roster.c] */
+ NULL, /* add_buddies */
+ mxit_remove_buddy, /* remove_buddy [roster.c] */
+ NULL, /* remove_buddies */
+ NULL, /* add_permit */
+ NULL, /* add_deny */
+ NULL, /* rem_permit */
+ NULL, /* rem_deny */
+ NULL, /* set_permit_deny */
+ mxit_chat_join, /* join_chat [multimx.c] */
+ mxit_chat_reject, /* reject chat invite [multimx.c] */
+ mxit_chat_name, /* get_chat_name [multimx.c] */
+ mxit_chat_invite, /* chat_invite [multimx.c] */
+ mxit_chat_leave, /* chat_leave [multimx.c] */
+ NULL, /* chat_whisper */
+ mxit_chat_send, /* chat_send [multimx.c] */
+ mxit_keepalive, /* keepalive */
+ mxit_register, /* register_user */
+ NULL, /* get_cb_info */
+ NULL, /* get_cb_away */
+ mxit_buddy_alias, /* alias_buddy [roster.c] */
+ mxit_buddy_group, /* group_buddy [roster.c] */
+ mxit_rename_group, /* rename_group [roster.c] */
+ mxit_free_buddy, /* buddy_free */
+ NULL, /* convo_closed */
+ NULL, /* normalize */
+ mxit_set_buddy_icon, /* set_buddy_icon */
+ NULL, /* remove_group */ // TODO: Add function to move all contacts out of this group (cmd=30 - remove group)?
+ NULL, /* get_cb_real_name */
+ NULL, /* set_chat_topic */
+ NULL, /* find_blist_chat */
+ NULL, /* roomlist_get_list */
+ NULL, /* roomlist_cancel */
+ NULL, /* roomlist_expand_category */
+ mxit_xfer_enabled, /* can_receive_file [filexfer.c] */
+ mxit_xfer_tx, /* send_file [filexfer.c */
+ mxit_xfer_new, /* new_xfer [filexfer.c] */
+ mxit_offline_message, /* offline_message */
+ NULL, /* whiteboard_prpl_ops */
+ NULL, /* send_raw */
+ NULL, /* roomlist_room_serialize */
+ NULL, /* unregister_user */
+ NULL, /* send_attention */
+ NULL, /* attention_types */
+ sizeof( PurplePluginProtocolInfo ), /* struct_size */
+ mxit_get_text_table, /* get_account_text_table */
+ NULL,
+ NULL
+};
+
+
+static PurplePluginInfo plugin_info = {
+ PURPLE_PLUGIN_MAGIC, /* purple magic, this must always be PURPLE_PLUGIN_MAGIC */
+ PURPLE_MAJOR_VERSION, /* libpurple version */
+ PURPLE_MINOR_VERSION, /* libpurple version */
+ PURPLE_PLUGIN_PROTOCOL, /* plugin type (connecting to another network) */
+ NULL, /* UI requirement (NULL for core plugin) */
+ 0, /* plugin flags (zero is default) */
+ NULL, /* plugin dependencies (set this value to NULL no matter what) */
+ PURPLE_PRIORITY_DEFAULT, /* libpurple priority */
+
+ MXIT_PLUGIN_ID, /* plugin id (must be unique) */
+ MXIT_PLUGIN_NAME, /* plugin name (this will be displayed in the UI) */
+ MXIT_PLUGIN_VERSION, /* version of the plugin */
+
+ MXIT_PLUGIN_SUMMARY, /* short summary of the plugin */
+ MXIT_PLUGIN_DESC, /* description of the plugin (can be long) */
+ MXIT_PLUGIN_EMAIL, /* plugin author name and email address */
+ MXIT_PLUGIN_WWW, /* plugin website (to find new versions and reporting of bugs) */
+
+ NULL, /* function pointer for loading the plugin */
+ NULL, /* function pointer for unloading the plugin */
+ NULL, /* function pointer for destroying the plugin */
+
+ NULL, /* pointer to an UI-specific struct */
+ &proto_info, /* pointer to either a PurplePluginLoaderInfo or PurplePluginProtocolInfo struct */
+ NULL, /* pointer to a PurplePluginUiInfo struct */
+ mxit_actions, /* function pointer where you can define plugin-actions */
+
+ /* padding */
+ NULL, /* pointer reserved for future use */
+ NULL, /* pointer reserved for future use */
+ NULL, /* pointer reserved for future use */
+ NULL /* pointer reserved for future use */
+};
+
+
+/*------------------------------------------------------------------------
+ * Initialising the MXit plugin.
+ *
+ * @param plugin The plugin object
+ */
+static void init_plugin( PurplePlugin* plugin )
+{
+ PurpleAccountOption* option;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "Loading MXit libPurple plugin...\n" );
+
+ /* Configuration options */
+
+ /* WAP server (reference: "libpurple/accountopt.h") */
+ option = purple_account_option_string_new( _( "WAP Server" ), MXIT_CONFIG_WAPSERVER, DEFAULT_WAPSITE );
+ proto_info.protocol_options = g_list_append( proto_info.protocol_options, option );
+
+ option = purple_account_option_bool_new( _( "Connect via HTTP" ), MXIT_CONFIG_USE_HTTP, FALSE );
+ proto_info.protocol_options = g_list_append( proto_info.protocol_options, option );
+
+ option = purple_account_option_bool_new( _( "Enable splash-screen popup" ), MXIT_CONFIG_SPLASHPOPUP, FALSE );
+ proto_info.protocol_options = g_list_append( proto_info.protocol_options, option );
+
+ g_assert( sizeof( struct raw_chunk ) == 5 );
+}
+
+PURPLE_INIT_PLUGIN( mxit, init_plugin, plugin_info );
+
diff --git a/libpurple/protocols/mxit/mxit.h b/libpurple/protocols/mxit/mxit.h
new file mode 100644
index 0000000000..6f574af86c
--- /dev/null
+++ b/libpurple/protocols/mxit/mxit.h
@@ -0,0 +1,197 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- MXit libPurple plugin API --
+ *
+ * Pieter Loubser <libpurple@mxit.com>
+ *
+ * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
+ * <http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#ifndef _MXIT_H_
+#define _MXIT_H_
+
+
+/* internationalize feedback strings */
+#ifndef _
+#ifdef GETTEXT_PACKAGE
+#include <glib/gi18n-lib.h>
+#else
+#define _( x ) ( x )
+#endif
+#endif
+
+
+#if defined( __APPLE__ )
+/* apple architecture */
+#ifndef HOST_NAME_MAX
+#define HOST_NAME_MAX 512
+#endif
+#elif defined( _WIN32 )
+/* windows architecture */
+#define HOST_NAME_MAX 512
+#include "libc_interface.h"
+#elif defined( __linux__ )
+/* linux architecture */
+#include <net/if.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#else
+/* other architecture */
+#ifndef HOST_NAME_MAX
+#define HOST_NAME_MAX 512
+#endif
+#endif
+
+
+#include "protocol.h"
+#include "profile.h"
+
+
+/* Plugin details */
+#define MXIT_PLUGIN_ID "prpl-loubserp-mxit"
+#define MXIT_PLUGIN_NAME "MXit"
+#define MXIT_PLUGIN_VERSION "2.2.0"
+#define MXIT_PLUGIN_EMAIL "Pieter Loubser <libpurple@mxit.com>"
+#define MXIT_PLUGIN_WWW "http://www.mxit.com"
+#define MXIT_PLUGIN_SUMMARY "MXit Protocol Plugin"
+#define MXIT_PLUGIN_DESC "MXit"
+
+#define MXIT_HTTP_USERAGENT "libpurple-"MXIT_PLUGIN_VERSION
+
+
+/* default connection settings */
+#define DEFAULT_SERVER "stream.mxit.co.za"
+#define DEFAULT_PORT 9119
+#define DEFAULT_WAPSITE "http://www.mxit.com"
+#define DEFAULT_HTTP_SERVER "http://int.poll.mxit.com:80/mxit"
+
+
+/* Purple account configuration variable names */
+#define MXIT_CONFIG_STATE "state"
+#define MXIT_CONFIG_WAPSERVER "wap_server"
+#define MXIT_CONFIG_DISTCODE "distcode"
+#define MXIT_CONFIG_CLIENTKEY "clientkey"
+#define MXIT_CONFIG_DIALCODE "dialcode"
+#define MXIT_CONFIG_SERVER_ADDR "server"
+#define MXIT_CONFIG_SERVER_PORT "port"
+#define MXIT_CONFIG_HTTPSERVER "httpserver"
+#define MXIT_CONFIG_SPLASHID "splashid"
+#define MXIT_CONFIG_SPLASHCLICK "splashclick"
+#define MXIT_CONFIG_SPLASHPOPUP "splashpopup"
+#define MXIT_CONFIG_COUNTRYCODE "cc"
+#define MXIT_CONFIG_LOCALE "locale"
+#define MXIT_CONFIG_USE_HTTP "use_http"
+
+
+/* account states */
+#define MXIT_STATE_LOGIN 0x00
+#define MXIT_STATE_REGISTER1 0x01
+#define MXIT_STATE_REGISTER2 0x02
+
+
+/* Client session flags */
+#define MXIT_FLAG_CONNECTED 0x01 /* established connection to the server */
+#define MXIT_FLAG_LOGGEDIN 0x02 /* user currently logged in */
+#define MXIT_FLAG_FIRSTROSTER 0x04 /* set to true once the first roster update has been recevied and processed */
+
+
+/* define this to enable the link clicking support */
+#define MXIT_LINK_CLICK
+
+
+#ifdef MXIT_LINK_CLICK
+#define MXIT_LINK_PREFIX "gopher://"
+#define MXIT_LINK_KEY "MXIT"
+#endif
+
+
+#define ARRAY_SIZE( x ) ( sizeof( x ) / sizeof( x[0] ) )
+
+
+/*
+ * data structure containing all MXit session information
+ */
+struct MXitSession {
+ /* socket connection */
+ char server[HOST_NAME_MAX]; /* MXit server name to connect to */
+ int port; /* MXit server port to connect on */
+ int fd; /* connection file descriptor */
+
+ /* http connection */
+ gboolean http; /* connect to MXit via HTTP and not by socket */
+ char http_server[HOST_NAME_MAX]; /* MXit HTTP server */
+ unsigned int http_sesid; /* HTTP session id */
+ unsigned int http_seqno; /* HTTP request sequence number */
+ guint http_timer_id; /* timer resource id (pidgin) */
+ int http_interval; /* poll inverval */
+ time_t http_last_poll; /* the last time a poll has been sent */
+ guint http_handler; /* HTTP connection handler */
+ void* http_out_req; /* HTTP outstanding request */
+
+ /* client */
+ struct login_data* logindata;
+ char* encpwd; /* encrypted password */
+ char distcode[64]; /* distribution code */
+ char clientkey[16]; /* client key */
+ char dialcode[8]; /* dialing code */
+ short flags; /* client session flags (see above) */
+
+ /* personal (profile) */
+ struct MXitProfile* profile; /* user's profile information */
+ int mood; /* user's current mood */
+
+ /* libpurple */
+ PurpleAccount* acc; /* pointer to the libpurple internal account struct */
+ PurpleConnection* con; /* pointer to the libpurple internal connection struct */
+
+ /* transmit */
+ struct tx_queue queue; /* transmit packet queue (FIFO mode) */
+ time_t last_tx; /* timestamp of last packet sent */
+ int outack; /* outstanding ack packet */
+ guint q_timer; /* timer handler for managing queue */
+
+ /* receive */
+ char rx_lbuf[16]; /* receive byte buffer (socket packet length) */
+ char rx_dbuf[CP_MAX_PACKET]; /* receive byte buffer (raw data) */
+ unsigned int rx_i; /* receive buffer current index */
+ int rx_res; /* amount of bytes still outstanding for the current packet */
+ char rx_state; /* current receiver state */
+ time_t last_rx; /* timestamp of last packet received */
+ GList* active_chats; /* list of all our contacts we received messages from (active chats) */
+
+ /* groupchat */
+ GList* rooms; /* active groupchat rooms */
+
+ /* inline images */
+ GHashTable* iimages; /* table which maps inline images (including emoticons) to purple's imgstore id's */
+};
+
+
+char* mxit_status_text( PurpleBuddy* buddy );
+void mxit_enable_signals( struct MXitSession* session );
+
+#ifdef MXIT_LINK_CLICK
+void mxit_register_uri_handler();
+#endif
+
+
+#endif /* _MXIT_H_ */
+
diff --git a/libpurple/protocols/mxit/profile.c b/libpurple/protocols/mxit/profile.c
new file mode 100644
index 0000000000..321a394a4f
--- /dev/null
+++ b/libpurple/protocols/mxit/profile.c
@@ -0,0 +1,160 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- user profile's --
+ *
+ * Andrew Victor <libpurple@mxit.com>
+ *
+ * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
+ * <http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include <ctype.h>
+#include <string.h>
+
+#include "purple.h"
+
+#include "mxit.h"
+#include "profile.h"
+#include "roster.h"
+
+
+/*------------------------------------------------------------------------
+ * Returns true if it is a valid date.
+ *
+ * @param bday Date-of-Birth string
+ * @return TRUE if valid, else FALSE
+ */
+gboolean validateDate( const char* bday )
+{
+ struct tm* tm;
+ time_t t;
+ int cur_year;
+ int max_days[13] = { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+ char date[16];
+ int year;
+ int month;
+ int day;
+
+ /* validate length */
+ if ( strlen( bday ) != 10 ) {
+ return FALSE;
+ }
+
+ /* validate the format */
+ if ( ( !isdigit( bday[0] ) ) || ( !isdigit( bday[1] ) ) || ( !isdigit( bday[2] ) ) || ( !isdigit( bday[3] ) ) || /* year */
+ ( bday[4] != '-' ) ||
+ ( !isdigit( bday[5] ) ) || ( !isdigit( bday[6] ) ) || /* month */
+ ( bday[7] != '-' ) ||
+ ( !isdigit( bday[8] ) ) || ( !isdigit( bday[9] ) ) ) { /* day */
+ return FALSE;
+ }
+
+ /* convert */
+ t = time( NULL );
+ tm = gmtime( &t );
+ cur_year = tm->tm_year + 1900;
+ memcpy( date, bday, 10 );
+ date[4] = '\0';
+ date[7] = '\0';
+ date[10] = '\0';
+ year = atoi( &date[0] );
+ month = atoi( &date[5] );
+ day = atoi( &date[8] );
+
+ /* validate month */
+ if ( ( month < 1 ) || ( month > 12 ) ) {
+ return FALSE;
+ }
+
+ /* validate day */
+ if ( ( day < 1 ) || ( day > max_days[month] ) ) {
+ return FALSE;
+ }
+
+ /* validate year */
+ if ( ( year < ( cur_year - 100 ) ) || ( year >= cur_year ) ) {
+ /* you are either tooo old or tooo young to join mxit... sorry */
+ return FALSE;
+ }
+
+ /* special case leap-year */
+ if ( ( year % 4 != 0 ) && ( month == 2 ) && ( day == 29 ) ) {
+ /* cannot have 29 days in February in non leap-years! */
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+/*------------------------------------------------------------------------
+ * Display the profile information.
+ *
+ * @param session The MXit session object
+ * @param username The username who's profile information this is
+ * @param profile The profile
+ */
+void mxit_show_profile( struct MXitSession* session, const char* username, struct MXitProfile* profile )
+{
+ PurpleNotifyUserInfo* info = purple_notify_user_info_new();
+ struct contact* contact = NULL;
+ PurpleBuddy* buddy;
+
+ buddy = purple_find_buddy( session->acc, username );
+ if ( buddy ) {
+ purple_notify_user_info_add_pair( info, _( "Alias" ), buddy->alias );
+ purple_notify_user_info_add_section_break( info );
+ contact = buddy->proto_data;
+ }
+
+ purple_notify_user_info_add_pair( info, _( "Nick Name" ), profile->nickname );
+ purple_notify_user_info_add_pair( info, _( "Birthday" ), profile->birthday );
+ purple_notify_user_info_add_pair( info, _( "Gender" ), profile->male ? _( "Male" ) : _( "Female" ) );
+ purple_notify_user_info_add_pair( info, _( "Hidden Number" ), profile->hidden ? _( "Yes" ) : _( "No" ) );
+
+ purple_notify_user_info_add_section_break( info );
+
+ /* optional information */
+ purple_notify_user_info_add_pair( info, _( "Title" ), profile->title );
+ purple_notify_user_info_add_pair( info, _( "First Name" ), profile->firstname );
+ purple_notify_user_info_add_pair( info, _( "Last Name" ), profile->lastname );
+ purple_notify_user_info_add_pair( info, _( "Email" ), profile->email );
+
+ purple_notify_user_info_add_section_break( info );
+
+ if ( contact ) {
+ /* presence */
+ purple_notify_user_info_add_pair( info, _( "Status" ), mxit_convert_presence_to_name( contact->presence ) );
+
+ /* mood */
+ if ( contact->mood != MXIT_MOOD_NONE )
+ purple_notify_user_info_add_pair( info, _( "Mood" ), mxit_convert_mood_to_name( contact->mood ) );
+ else
+ purple_notify_user_info_add_pair( info, _( "Mood" ), _( "None" ) );
+
+ /* status message */
+ if ( contact->statusMsg )
+ purple_notify_user_info_add_pair( info, _( "Status Message" ), contact->statusMsg );
+
+ /* subscription type */
+ purple_notify_user_info_add_pair( info, _( "Subscription" ), mxit_convert_subtype_to_name( contact->subtype ) );
+ }
+
+ purple_notify_userinfo( session->con, username, info, NULL, NULL );
+ purple_notify_user_info_destroy( info );
+}
diff --git a/libpurple/protocols/mxit/profile.h b/libpurple/protocols/mxit/profile.h
new file mode 100644
index 0000000000..0e36cc7e83
--- /dev/null
+++ b/libpurple/protocols/mxit/profile.h
@@ -0,0 +1,56 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- user profile's --
+ *
+ * Andrew Victor <libpurple@mxit.com>
+ *
+ * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
+ * <http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#ifndef _MXIT_PROFILE_H_
+#define _MXIT_PROFILE_H_
+
+#include <glib.h>
+
+
+struct MXitProfile {
+ /* required */
+ char loginname[64]; /* name user uses to log into MXit with (aka 'mxitid') */
+ char nickname[64]; /* user's own display name (aka 'nickname', aka 'fullname', aka 'alias') in MXit */
+ char birthday[16]; /* user's birthday "YYYY-MM-DD" */
+ gboolean male; /* true if the user's gender is male (otherwise female) */
+ char pin[16]; /* user's password */
+
+ /* optional */
+ char title[32]; /* user's title */
+ char firstname[64]; /* user's first name */
+ char lastname[64]; /* user's last name (aka 'surname') */
+ char email[64]; /* user's email address */
+ char mobilenr[21]; /* user's mobile number */
+
+ gboolean hidden; /* set if the user's msisdn should remain hidden */
+};
+
+struct MXitSession;
+void mxit_show_profile( struct MXitSession* session, const char* username, struct MXitProfile* profile );
+
+gboolean validateDate( const char* bday );
+
+
+#endif /* _MXIT_PROFILE_H_ */
diff --git a/libpurple/protocols/mxit/protocol.c b/libpurple/protocols/mxit/protocol.c
new file mode 100644
index 0000000000..2978d28b34
--- /dev/null
+++ b/libpurple/protocols/mxit/protocol.c
@@ -0,0 +1,2442 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- MXit client protocol implementation --
+ *
+ * Pieter Loubser <libpurple@mxit.com>
+ *
+ * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
+ * <http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include "purple.h"
+
+#include "protocol.h"
+#include "mxit.h"
+#include "roster.h"
+#include "chunk.h"
+#include "filexfer.h"
+#include "markup.h"
+#include "multimx.h"
+#include "splashscreen.h"
+#include "login.h"
+#include "formcmds.h"
+#include "http.h"
+
+
+#define MXIT_MS_OFFSET 3
+
+/* configure the right record terminator char to use */
+#define CP_REC_TERM ( ( session->http ) ? CP_HTTP_REC_TERM : CP_SOCK_REC_TERM )
+
+
+
+/*------------------------------------------------------------------------
+ * Display a notification popup message to the user.
+ *
+ * @param type The type of notification:
+ * - info: PURPLE_NOTIFY_MSG_INFO
+ * - warning: PURPLE_NOTIFY_MSG_WARNING
+ * - error: PURPLE_NOTIFY_MSG_ERROR
+ * @param heading Heading text
+ * @param message Message text
+ */
+void mxit_popup( int type, const char* heading, const char* message )
+{
+ /* (reference: "libpurple/notify.h") */
+ purple_notify_message( NULL, type, _( MXIT_POPUP_WIN_NAME ), heading, message, NULL, NULL );
+}
+
+
+/*------------------------------------------------------------------------
+ * For compatibility with legacy clients, all usernames are sent from MXit with a domain
+ * appended. For MXit contacts, this domain is set to "@m". This function strips
+ * those fake domains.
+ *
+ * @param username The username of the contact
+ */
+void mxit_strip_domain( char* username )
+{
+ if ( g_str_has_suffix( username, "@m" ) )
+ username[ strlen(username) - 2 ] = '\0';
+}
+
+
+/*------------------------------------------------------------------------
+ * Dump a byte buffer to the console for debugging purposes.
+ *
+ * @param buf The data
+ * @param len The data length
+ */
+void dump_bytes( struct MXitSession* session, const char* buf, int len )
+{
+ char msg[( len * 3 ) + 1];
+ int i;
+
+ memset( msg, 0x00, sizeof( msg ) );
+
+ for ( i = 0; i < len; i++ ) {
+ if ( buf[i] == CP_REC_TERM ) /* record terminator */
+ msg[i] = '!';
+ else if ( buf[i] == CP_FLD_TERM ) /* field terminator */
+ msg[i] = '^';
+ else if ( buf[i] == CP_PKT_TERM ) /* packet terminator */
+ msg[i] = '@';
+ else if ( buf[i] < 0x20 )
+ msg[i] = '_';
+ else
+ msg[i] = buf[i];
+
+ }
+
+ purple_debug_info( MXIT_PLUGIN_ID, "DUMP: '%s'\n", msg );
+}
+
+
+/*------------------------------------------------------------------------
+ * Determine if we have an active chat with a specific contact
+ *
+ * @param session The MXit session object
+ * @param who The contact name
+ * @return Return true if we have an active chat with the contact
+ */
+gboolean find_active_chat( const GList* chats, const char* who )
+{
+ const GList* list = chats;
+ const char* chat = NULL;
+
+ while ( list ) {
+ chat = (const char*) list->data;
+
+ if ( strcmp( chat, who ) == 0 )
+ return TRUE;
+
+ list = g_list_next( list );
+ }
+
+ return FALSE;
+}
+
+
+/*========================================================================================================================
+ * Low-level Packet transmission
+ */
+
+/*------------------------------------------------------------------------
+ * Remove next packet from transmission queue.
+ *
+ * @param session The MXit session object
+ * @return The next packet for transmission (or NULL)
+ */
+static struct tx_packet* pop_tx_packet( struct MXitSession* session )
+{
+ struct tx_packet* packet = NULL;
+
+ if ( session->queue.count > 0 ) {
+ /* dequeue the next packet */
+ packet = session->queue.packets[session->queue.rd_i];
+ session->queue.packets[session->queue.rd_i] = NULL;
+ session->queue.rd_i = ( session->queue.rd_i + 1 ) % MAX_QUEUE_SIZE;
+ session->queue.count--;
+ }
+
+ return packet;
+}
+
+
+/*------------------------------------------------------------------------
+ * Add packet to transmission queue.
+ *
+ * @param session The MXit session object
+ * @param packet The packet to transmit
+ * @return Return TRUE if packet was enqueue, or FALSE if queue is full.
+ */
+static gboolean push_tx_packet( struct MXitSession* session, struct tx_packet* packet )
+{
+ if ( session->queue.count < MAX_QUEUE_SIZE ) {
+ /* enqueue packet */
+ session->queue.packets[session->queue.wr_i] = packet;
+ session->queue.wr_i = ( session->queue.wr_i + 1 ) % MAX_QUEUE_SIZE;
+ session->queue.count++;
+ return TRUE;
+ }
+ else
+ return FALSE; /* queue is full */
+}
+
+
+/*------------------------------------------------------------------------
+ * Deallocate transmission packet.
+ *
+ * @param packet The packet to deallocate.
+ */
+static void free_tx_packet( struct tx_packet* packet )
+{
+ g_free( packet->data );
+ g_free( packet );
+ packet = NULL;
+}
+
+
+/*------------------------------------------------------------------------
+ * Flush all the packets from the tx queue and release the resources.
+ *
+ * @param session The MXit session object
+ */
+static void flush_queue( struct MXitSession* session )
+{
+ struct tx_packet* packet;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "flushing the tx queue\n" );
+
+ while ( (packet = pop_tx_packet( session ) ) != NULL )
+ free_tx_packet( packet );
+}
+
+
+/*------------------------------------------------------------------------
+ * TX Step 3: Write the packet data to the TCP connection.
+ *
+ * @param fd The file descriptor
+ * @param pktdata The packet data
+ * @param pktlen The length of the packet data
+ * @return Return -1 on error, otherwise 0
+ */
+static int mxit_write_sock_packet( int fd, const char* pktdata, int pktlen )
+{
+ int written;
+ int res;
+
+ written = 0;
+ while ( written < pktlen ) {
+ res = write( fd, &pktdata[written], pktlen - written );
+ if ( res <= 0 ) {
+ /* error on socket */
+ if ( errno == EAGAIN )
+ continue;
+
+ purple_debug_error( MXIT_PLUGIN_ID, "Error while writing packet to MXit server (%i)\n", res );
+ return -1;
+ }
+ written += res;
+ }
+
+ return 0;
+}
+
+
+/*------------------------------------------------------------------------
+ * Callback called for handling a HTTP GET response
+ *
+ * @param url_data libPurple internal object (see purple_util_fetch_url_request)
+ * @param user_data The MXit session object
+ * @param url_text The data returned (could be NULL if error)
+ * @param len The length of the data returned (0 if error)
+ * @param error_message Descriptive error message
+ */
+static void mxit_cb_http_rx( PurpleUtilFetchUrlData* url_data, gpointer user_data, const gchar* url_text, gsize len, const gchar* error_message )
+{
+ struct MXitSession* session = (struct MXitSession*) user_data;
+
+ /* clear outstanding request */
+ session->http_out_req = NULL;
+
+ if ( ( !url_text ) || ( len == 0 ) ) {
+ /* error with request */
+ purple_debug_error( MXIT_PLUGIN_ID, "HTTP response error (%s)\n", error_message );
+ return;
+ }
+
+ /* convert the HTTP result */
+ memcpy( session->rx_dbuf, url_text, len );
+ session->rx_i = len;
+
+ mxit_parse_packet( session );
+}
+
+
+/*------------------------------------------------------------------------
+ * TX Step 3: Write the packet data to the HTTP connection (GET style).
+ *
+ * @param session The MXit session object
+ * @param pktdata The packet data
+ * @param pktlen The length of the packet data
+ * @return Return -1 on error, otherwise 0
+ */
+static void mxit_write_http_get( struct MXitSession* session, struct tx_packet* packet )
+{
+ char* part = NULL;
+ char* url = NULL;
+
+ if ( packet->datalen > 0 ) {
+ char* tmp = NULL;
+
+ tmp = g_strndup( packet->data, packet->datalen );
+ part = g_strdup( purple_url_encode( tmp ) );
+ g_free( tmp );
+ }
+
+ url = g_strdup_printf( "%s?%s%s", session->http_server, purple_url_encode( packet->header ), ( !part ) ? "" : part );
+
+#ifdef DEBUG_PROTOCOL
+ purple_debug_info( MXIT_PLUGIN_ID, "HTTP GET: '%s'\n", url );
+#endif
+
+ /* send the HTTP request */
+ session->http_out_req = purple_util_fetch_url_request( url, TRUE, MXIT_HTTP_USERAGENT, TRUE, NULL, FALSE, mxit_cb_http_rx, session );
+
+ g_free( url );
+ if ( part )
+ g_free( part );
+}
+
+
+/*------------------------------------------------------------------------
+ * TX Step 3: Write the packet data to the HTTP connection (POST style).
+ *
+ * @param session The MXit session object
+ * @param pktdata The packet data
+ * @param pktlen The length of the packet data
+ * @return Return -1 on error, otherwise 0
+ */
+static void mxit_write_http_post( struct MXitSession* session, struct tx_packet* packet )
+{
+ char request[256 + packet->datalen];
+ int reqlen;
+ char* host_name;
+ int host_port;
+ gboolean ok;
+
+ /* extract the HTTP host name and host port number to connect to */
+ ok = purple_url_parse( session->http_server, &host_name, &host_port, NULL, NULL, NULL );
+ if ( !ok ) {
+ purple_debug_error( MXIT_PLUGIN_ID, "HTTP POST error: (host name '%s' not valid)\n", session->http_server );
+ }
+
+ /* strip off the last '&' from the header */
+ packet->header[packet->headerlen - 1] = '\0';
+ packet->headerlen--;
+
+ /* build the HTTP request packet */
+ reqlen = g_snprintf( request, 256,
+ "POST %s?%s HTTP/1.1\r\n"
+ "User-Agent: " MXIT_HTTP_USERAGENT "\r\n"
+ "Content-Type: application/octet-stream\r\n"
+ "Host: %s\r\n"
+ "Content-Length: %" G_GSIZE_FORMAT "\r\n"
+ "\r\n",
+ session->http_server,
+ purple_url_encode( packet->header ),
+ host_name,
+ packet->datalen - MXIT_MS_OFFSET
+ );
+
+ /* copy over the packet body data (could be binary) */
+ memcpy( request + reqlen, packet->data + MXIT_MS_OFFSET, packet->datalen - MXIT_MS_OFFSET );
+ reqlen += packet->datalen;
+
+#ifdef DEBUG_PROTOCOL
+ purple_debug_info( MXIT_PLUGIN_ID, "HTTP POST:\n" );
+ dump_bytes( session, request, reqlen );
+#endif
+
+ /* send the request to the HTTP server */
+ mxit_http_send_request( session, host_name, host_port, request, reqlen );
+}
+
+
+/*------------------------------------------------------------------------
+ * TX Step 2: Handle the transmission of the packet to the MXit server.
+ *
+ * @param session The MXit session object
+ * @param packet The packet to transmit
+ */
+static void mxit_send_packet( struct MXitSession* session, struct tx_packet* packet )
+{
+ int res;
+
+ if ( !( session->flags & MXIT_FLAG_CONNECTED ) ) {
+ /* we are not connected so ignore all packets to be send */
+ purple_debug_error( MXIT_PLUGIN_ID, "Dropping TX packet (we are not connected)\n" );
+ return;
+ }
+
+ purple_debug_info( MXIT_PLUGIN_ID, "Packet send CMD:%i (%i)\n", packet->cmd, packet->headerlen + packet->datalen );
+#ifdef DEBUG_PROTOCOL
+ dump_bytes( session, packet->header, packet->headerlen );
+ dump_bytes( session, packet->data, packet->datalen );
+#endif
+
+ if ( !session->http ) {
+ /* socket connection */
+ char data[packet->datalen + packet->headerlen];
+ int datalen;
+
+ /* create raw data buffer */
+ memcpy( data, packet->header, packet->headerlen );
+ memcpy( data + packet->headerlen, packet->data, packet->datalen );
+ datalen = packet->headerlen + packet->datalen;
+
+ res = mxit_write_sock_packet( session->fd, data, datalen );
+ if ( res < 0 ) {
+ /* we must have lost the connection, so terminate it so that we can reconnect */
+ purple_connection_error( session->con, _( "We have lost the connection to MXit. Please reconnect." ) );
+ }
+ }
+ else {
+ /* http connection */
+
+ if ( packet->cmd == CP_CMD_MEDIA ) {
+ /* multimedia packets must be send with a HTTP POST */
+ mxit_write_http_post( session, packet );
+ }
+ else {
+ mxit_write_http_get( session, packet );
+ }
+ }
+
+ /* update the timestamp of the last-transmitted packet */
+ session->last_tx = time( NULL );
+
+ /*
+ * we need to remember that we are still waiting for the ACK from
+ * the server on this request
+ */
+ session->outack = packet->cmd;
+
+ /* free up the packet resources */
+ free_tx_packet( packet );
+}
+
+
+/*------------------------------------------------------------------------
+ * TX Step 1: Create a new Tx packet and queue it for sending.
+ *
+ * @param session The MXit session object
+ * @param data The packet data (payload)
+ * @param datalen The length of the packet data
+ * @param cmd The MXit command for this packet
+ */
+static void mxit_queue_packet( struct MXitSession* session, const char* data, int datalen, int cmd )
+{
+ struct tx_packet* packet;
+ char header[256];
+ int hlen;
+
+ /* create a packet for sending */
+ packet = g_new0( struct tx_packet, 1 );
+ packet->data = g_malloc0( datalen );
+ packet->cmd = cmd;
+ packet->headerlen = 0;
+
+ /* create generic packet header */
+ hlen = sprintf( header, "id=%s%c", session->acc->username, CP_REC_TERM ); /* client msisdn */
+
+ if ( session->http ) {
+ /* http connection only */
+ hlen += sprintf( header + hlen, "s=" );
+ if ( session->http_sesid > 0 ) {
+ hlen += sprintf( header + hlen, "%u%c", session->http_sesid, CP_FLD_TERM ); /* http session id */
+ }
+ session->http_seqno++;
+ hlen += sprintf( header + hlen, "%u%c", session->http_seqno, CP_REC_TERM ); /* http request sequence id */
+ }
+
+ hlen += sprintf( header + hlen, "cm=%i%c", cmd, CP_REC_TERM ); /* packet command */
+
+ if ( !session->http ) {
+ /* socket connection only */
+ packet->headerlen += sprintf( packet->header, "ln=%i%c", ( datalen + hlen ), CP_REC_TERM ); /* packet length */
+ }
+
+ /* copy the header to packet */
+ memcpy( packet->header + packet->headerlen, header, hlen );
+ packet->headerlen += hlen;
+
+ /* copy payload to packet */
+ if ( datalen > 0 )
+ memcpy( packet->data, data, datalen );
+ packet->datalen = datalen;
+
+
+ /*
+ * shortcut: first check if there are any commands still outstanding.
+ * if not, then we might as well just write this packet directly and
+ * skip the whole queueing thing
+ */
+ if ( session->outack == 0 ) {
+ /* no outstanding ACKs, so we might as well write it directly */
+ mxit_send_packet( session, packet );
+ }
+ else {
+ /* ACK still outstanding, so we need to queue this request until we have the ACK */
+
+ if ( ( packet->cmd == CP_CMD_PING ) || ( packet->cmd == CP_CMD_POLL ) ) {
+ /* we do NOT queue HTTP poll nor socket ping packets */
+ free_tx_packet( packet );
+ return;
+ }
+
+ purple_debug_info( MXIT_PLUGIN_ID, "queueing packet for later sending cmd=%i\n", cmd );
+ if ( !push_tx_packet( session, packet ) ) {
+ /* packet could not be queued for transmission */
+ mxit_popup( PURPLE_NOTIFY_MSG_ERROR, _( "Message Send Error" ), _( "Unable to process your request at this time" ) );
+ free_tx_packet( packet );
+ }
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Callback to manage the packet send queue (send next packet, timeout's, etc).
+ *
+ * @param session The MXit session object
+ */
+gboolean mxit_manage_queue( gpointer user_data )
+{
+ struct MXitSession* session = (struct MXitSession*) user_data;
+ struct tx_packet* packet = NULL;
+
+ if ( !( session->flags & MXIT_FLAG_CONNECTED ) ) {
+ /* we are not connected, so ignore the queue */
+ return TRUE;
+ }
+ else if ( session->outack > 0 ) {
+ /* we are still waiting for an outstanding ACK from the MXit server */
+ if ( session->last_tx <= time( NULL ) - MXIT_ACK_TIMEOUT ) {
+ /* ack timeout! so we close the connection here */
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_manage_queue: Timeout awaiting ACK for command '%X'\n", session->outack );
+ purple_connection_error( session->con, _( "Timeout while waiting for a response from the MXit server." ) );
+ }
+ return TRUE;
+ }
+
+ packet = pop_tx_packet( session );
+ if ( packet != NULL ) {
+ /* there was a packet waiting to be sent to the server, now is the time to do something about it */
+
+ /* send the packet to MXit server */
+ mxit_send_packet( session, packet );
+ }
+
+ return TRUE;
+}
+
+
+/*------------------------------------------------------------------------
+ * Callback to manage HTTP server polling (HTTP connections ONLY)
+ *
+ * @param session The MXit session object
+ */
+gboolean mxit_manage_polling( gpointer user_data )
+{
+ struct MXitSession* session = (struct MXitSession*) user_data;
+ gboolean poll = FALSE;
+ time_t now = time( NULL );
+ int polldiff;
+ int rxdiff;
+
+ if ( !( session->flags & MXIT_FLAG_LOGGEDIN ) ) {
+ /* we only poll if we are actually logged in */
+ return TRUE;
+ }
+
+ /* calculate the time differences */
+ rxdiff = now - session->last_rx;
+ polldiff = now - session->http_last_poll;
+
+ if ( rxdiff < MXIT_HTTP_POLL_MIN ) {
+ /* we received some reply a few moments ago, so reset the poll interval */
+ session->http_interval = MXIT_HTTP_POLL_MIN;
+ }
+ else if ( session->http_last_poll < ( now - session->http_interval ) ) {
+ /* time to poll again */
+ poll = TRUE;
+
+ /* back-off some more with the polling */
+ session->http_interval = session->http_interval + ( session->http_interval / 2 );
+ if ( session->http_interval > MXIT_HTTP_POLL_MAX )
+ session->http_interval = MXIT_HTTP_POLL_MAX;
+ }
+
+ /* debugging */
+ //purple_debug_info( MXIT_PLUGIN_ID, "POLL TIMER: %i (%i,%i)\n", session->http_interval, rxdiff, polldiff );
+
+ if ( poll ) {
+ /* send poll request */
+ session->http_last_poll = time( NULL );
+ mxit_send_poll( session );
+ }
+
+ return TRUE;
+}
+
+
+/*========================================================================================================================
+ * Send MXit operations.
+ */
+
+/*------------------------------------------------------------------------
+ * Send a ping/keepalive packet to MXit server.
+ *
+ * @param session The MXit session object
+ */
+void mxit_send_ping( struct MXitSession* session )
+{
+ /* queue packet for transmission */
+ mxit_queue_packet( session, NULL, 0, CP_CMD_PING );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a poll request to the HTTP server (HTTP connections ONLY).
+ *
+ * @param session The MXit session object
+ */
+void mxit_send_poll( struct MXitSession* session )
+{
+ /* queue packet for transmission */
+ mxit_queue_packet( session, NULL, 0, CP_CMD_POLL );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a logout packet to the MXit server.
+ *
+ * @param session The MXit session object
+ */
+void mxit_send_logout( struct MXitSession* session )
+{
+ /* queue packet for transmission */
+ mxit_queue_packet( session, NULL, 0, CP_CMD_LOGOUT );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a register packet to the MXit server.
+ *
+ * @param session The MXit session object
+ */
+void mxit_send_register( struct MXitSession* session )
+{
+ struct MXitProfile* profile = session->profile;
+ const char* locale;
+ char data[CP_MAX_PACKET];
+ int datalen;
+
+ locale = purple_account_get_string( session->acc, MXIT_CONFIG_LOCALE, MXIT_DEFAULT_LOCALE );
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=%s%c%s%c%i%c%s%c" /* "ms"=password\1version\1maxreplyLen\1name\1 */
+ "%s%c%i%c%s%c%s%c" /* dateOfBirth\1gender\1location\1capabilities\1 */
+ "%s%c%i%c%s%c%s", /* dc\1features\1dialingcode\1locale */
+ session->encpwd, CP_FLD_TERM, MXIT_CP_VERSION, CP_FLD_TERM, CP_MAX_FILESIZE, CP_FLD_TERM, profile->nickname, CP_FLD_TERM,
+ profile->birthday, CP_FLD_TERM, ( profile->male ) ? 1 : 0, CP_FLD_TERM, MXIT_DEFAULT_LOC, CP_FLD_TERM, MXIT_CP_CAP, CP_FLD_TERM,
+ session->distcode, CP_FLD_TERM, MXIT_CP_FEATURES, CP_FLD_TERM, session->dialcode, CP_FLD_TERM, locale
+ );
+
+ /* queue packet for transmission */
+ mxit_queue_packet( session, data, datalen, CP_CMD_REGISTER );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a login packet to the MXit server.
+ *
+ * @param session The MXit session object
+ */
+void mxit_send_login( struct MXitSession* session )
+{
+ const char* splashId;
+ const char* locale;
+ char data[CP_MAX_PACKET];
+ int datalen;
+
+ locale = purple_account_get_string( session->acc, MXIT_CONFIG_LOCALE, MXIT_DEFAULT_LOCALE );
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=%s%c%s%c%i%c" /* "ms"=password\1version\1getContacts\1 */
+ "%s%c%s%c%i%c" /* capabilities\1dc\1features\1 */
+ "%s%c%s", /* dialingcode\1locale */
+ session->encpwd, CP_FLD_TERM, MXIT_CP_VERSION, CP_FLD_TERM, 1, CP_FLD_TERM,
+ MXIT_CP_CAP, CP_FLD_TERM, session->distcode, CP_FLD_TERM, MXIT_CP_FEATURES, CP_FLD_TERM,
+ session->dialcode, CP_FLD_TERM, locale
+ );
+
+ /* include "custom resource" information */
+ splashId = splash_current( session );
+ if ( splashId != NULL )
+ datalen += sprintf( data + datalen, "%ccr=%s", CP_REC_TERM, splashId );
+
+ /* queue packet for transmission */
+ mxit_queue_packet( session, data, datalen, CP_CMD_LOGIN );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a chat message packet to the MXit server.
+ *
+ * @param session The MXit session object
+ * @param to The username of the recipient
+ * @param msg The message text
+ */
+void mxit_send_message( struct MXitSession* session, const char* to, const char* msg, gboolean parse_markup )
+{
+ char data[CP_MAX_PACKET];
+ char* markuped_msg;
+ int datalen;
+ int msgtype = CP_MSGTYPE_NORMAL;
+
+ /* first we need to convert the markup from libPurple to MXit format */
+ if ( parse_markup )
+ markuped_msg = mxit_convert_markup_tx( msg, &msgtype );
+ else
+ markuped_msg = g_strdup( msg );
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=%s%c%s%c%i%c%i", /* "ms"=jid\1msg\1type\1flags */
+ to, CP_FLD_TERM, markuped_msg, CP_FLD_TERM, msgtype, CP_FLD_TERM, CP_MSG_MARKUP | CP_MSG_EMOTICON
+ );
+
+ /* free the resources */
+ g_free( markuped_msg );
+
+ /* queue packet for transmission */
+ mxit_queue_packet( session, data, datalen, CP_CMD_TX_MSG );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a extended profile request packet to the MXit server.
+ *
+ * @param session The MXit session object
+ * @param username Username who's profile is being requested (NULL = our own)
+ * @param nr_attribs Number of attributes being requested
+ * @param attributes The names of the attributes
+ */
+void mxit_send_extprofile_request( struct MXitSession* session, const char* username, unsigned int nr_attrib, const char* attribute[] )
+{
+ char data[CP_MAX_PACKET];
+ int datalen;
+ unsigned int i;
+
+ datalen = sprintf( data, "ms=%s%c%i", /* "ms="mxitid\1nr_attributes */
+ (username ? username : ""), CP_FLD_TERM, nr_attrib);
+
+ /* add attributes */
+ for ( i = 0; i < nr_attrib; i++ )
+ datalen += sprintf( data + datalen, "%c%s", CP_FLD_TERM, attribute[i] );
+
+ /* queue packet for transmission */
+ mxit_queue_packet( session, data, datalen, CP_CMD_EXTPROFILE_GET );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send an update profile packet to the MXit server.
+ *
+ * @param session The MXit session object
+ * @param password The new password to be used for logging in (optional)
+ * @param nr_attrib The number of attributes
+ * @param attributes String containing the attributes and settings seperated by '0x01'
+ */
+void mxit_send_extprofile_update( struct MXitSession* session, const char* password, unsigned int nr_attrib, const char* attributes )
+{
+ char data[CP_MAX_PACKET];
+ gchar** parts;
+ int datalen;
+ unsigned int i;
+
+ parts = g_strsplit( attributes, "\01", ( MXIT_MAX_ATTRIBS * 3 ) );
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=%s%c%i", /* "ms"=password\1nr_attibutes */
+ ( password ) ? password : "", CP_FLD_TERM, nr_attrib
+ );
+
+ /* add attributes */
+ for ( i = 1; i < nr_attrib * 3; i+=3 )
+ datalen += sprintf( data + datalen, "%c%s%c%s%c%s", /* \1name\1type\1value */
+ CP_FLD_TERM, parts[i], CP_FLD_TERM, parts[i + 1], CP_FLD_TERM, parts[i + 2] );
+
+ /* queue packet for transmission */
+ mxit_queue_packet( session, data, datalen, CP_CMD_EXTPROFILE_SET );
+
+ /* freeup the memory */
+ g_strfreev( parts );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a presence update packet to the MXit server.
+ *
+ * @param session The MXit session object
+ * @param presence The presence (as per MXit types)
+ * @param statusmsg The status message (can be NULL)
+ */
+void mxit_send_presence( struct MXitSession* session, int presence, const char* statusmsg )
+{
+ char data[CP_MAX_PACKET];
+ int datalen;
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=%i%c", /* "ms"=show\1status */
+ presence, CP_FLD_TERM
+ );
+
+ /* append status message (if one is set) */
+ if ( statusmsg )
+ datalen += sprintf( data + datalen, "%s", statusmsg );
+
+ /* queue packet for transmission */
+ mxit_queue_packet( session, data, datalen, CP_CMD_STATUS );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a mood update packet to the MXit server.
+ *
+ * @param session The MXit session object
+ * @param mood The mood (as per MXit types)
+ */
+void mxit_send_mood( struct MXitSession* session, int mood )
+{
+ char data[CP_MAX_PACKET];
+ int datalen;
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=%i", /* "ms"=mood */
+ mood
+ );
+
+ /* queue packet for transmission */
+ mxit_queue_packet( session, data, datalen, CP_CMD_MOOD );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send an invite contact packet to the MXit server.
+ *
+ * @param session The MXit session object
+ * @param username The username of the contact being invited
+ * @param alias Our alias for the contact
+ * @param groupname Group in which contact should be stored.
+ */
+void mxit_send_invite( struct MXitSession* session, const char* username, const char* alias, const char* groupname )
+{
+ char data[CP_MAX_PACKET];
+ int datalen;
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=%s%c%s%c%s%c%i%c%s", /* "ms"=group\1username\1alias\1type\1msg */
+ groupname, CP_FLD_TERM, username, CP_FLD_TERM, alias,
+ CP_FLD_TERM, MXIT_TYPE_MXIT, CP_FLD_TERM, ""
+ );
+
+ /* queue packet for transmission */
+ mxit_queue_packet( session, data, datalen, CP_CMD_INVITE );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a remove contact packet to the MXit server.
+ *
+ * @param session The MXit session object
+ * @param username The username of the contact being removed
+ */
+void mxit_send_remove( struct MXitSession* session, const char* username )
+{
+ char data[CP_MAX_PACKET];
+ int datalen;
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=%s", /* "ms"=username */
+ username
+ );
+
+ /* queue packet for transmission */
+ mxit_queue_packet( session, data, datalen, CP_CMD_REMOVE );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send an accept subscription (invite) packet to the MXit server.
+ *
+ * @param session The MXit session object
+ * @param username The username of the contact being accepted
+ * @param alias Our alias for the contact
+ */
+void mxit_send_allow_sub( struct MXitSession* session, const char* username, const char* alias )
+{
+ char data[CP_MAX_PACKET];
+ int datalen;
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=%s%c%s%c%s", /* "ms"=username\1group\1alias */
+ username, CP_FLD_TERM, "", CP_FLD_TERM, alias
+ );
+
+ /* queue packet for transmission */
+ mxit_queue_packet( session, data, datalen, CP_CMD_ALLOW );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send an deny subscription (invite) packet to the MXit server.
+ *
+ * @param session The MXit session object
+ * @param username The username of the contact being denied
+ */
+void mxit_send_deny_sub( struct MXitSession* session, const char* username )
+{
+ char data[CP_MAX_PACKET];
+ int datalen;
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=%s", /* "ms"=username */
+ username
+ );
+
+ /* queue packet for transmission */
+ mxit_queue_packet( session, data, datalen, CP_CMD_DENY );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send an update contact packet to the MXit server.
+ *
+ * @param session The MXit session object
+ * @param username The username of the contact being denied
+ * @param alias Our alias for the contact
+ * @param groupname Group in which contact should be stored.
+ */
+void mxit_send_update_contact( struct MXitSession* session, const char* username, const char* alias, const char* groupname )
+{
+ char data[CP_MAX_PACKET];
+ int datalen;
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=%s%c%s%c%s", /* "ms"=groupname\1username\1alias */
+ groupname, CP_FLD_TERM, username, CP_FLD_TERM, alias
+ );
+
+ /* queue packet for transmission */
+ mxit_queue_packet( session, data, datalen, CP_CMD_UPDATE );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a splash-screen click event packet.
+ *
+ * @param session The MXit session object
+ * @param splashid The identifier of the splash-screen
+ */
+void mxit_send_splashclick( struct MXitSession* session, const char* splashid )
+{
+ char data[CP_MAX_PACKET];
+ int datalen;
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=%s", /* "ms"=splashId */
+ splashid
+ );
+
+ /* queue packet for transmission */
+ mxit_queue_packet( session, data, datalen, CP_CMD_SPLASHCLICK );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send packet to create a MultiMX room.
+ *
+ * @param session The MXit session object
+ * @param groupname Name of the room to create
+ * @param nr_usernames Number of users in initial invite
+ * @param usernames The usernames of the users in the initial invite
+ */
+void mxit_send_groupchat_create( struct MXitSession* session, const char* groupname, int nr_usernames, const char* usernames[] )
+{
+ char data[CP_MAX_PACKET];
+ int datalen;
+ int i;
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=%s%c%i", /* "ms"=roomname\1nr_jids\1jid0\1..\1jidN */
+ groupname, CP_FLD_TERM, nr_usernames
+ );
+
+ /* add usernames */
+ for ( i = 0; i < nr_usernames; i++ )
+ datalen += sprintf( data + datalen, "%c%s", CP_FLD_TERM, usernames[i] );
+
+ /* queue packet for transmission */
+ mxit_queue_packet( session, data, datalen, CP_CMD_GRPCHAT_CREATE );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send packet to invite users to existing MultiMX room.
+ *
+ * @param session The MXit session object
+ * @param roomid The unique RoomID for the MultiMx room.
+ * @param nr_usernames Number of users being invited
+ * @param usernames The usernames of the users being invited
+ */
+
+void mxit_send_groupchat_invite( struct MXitSession* session, const char* roomid, int nr_usernames, const char* usernames[] )
+{
+ char data[CP_MAX_PACKET];
+ int datalen;
+ int i;
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=%s%c%i", /* "ms"=roomid\1nr_jids\1jid0\1..\1jidN */
+ roomid, CP_FLD_TERM, nr_usernames
+ );
+
+ /* add usernames */
+ for ( i = 0; i < nr_usernames; i++ )
+ datalen += sprintf( data + datalen, "%c%s", CP_FLD_TERM, usernames[i] );
+
+ /* queue packet for transmission */
+ mxit_queue_packet( session, data, datalen, CP_CMD_GRPCHAT_INVITE );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a "send file direct" multimedia packet.
+ *
+ * @param session The MXit session object
+ * @param username The username of the recipient
+ * @param filename The name of the file being sent
+ * @param buf The content of the file
+ * @param buflen The length of the file contents
+ */
+void mxit_send_file( struct MXitSession* session, const char* username, const char* filename, const unsigned char* buf, int buflen )
+{
+ char data[CP_MAX_PACKET];
+ int datalen = 0;
+ struct raw_chunk* chunk;
+ int size;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "SENDING FILE '%s' of %i bytes to user '%s'\n", filename, buflen, username );
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=" );
+
+ /* map chunk header over data buffer */
+ chunk = (struct raw_chunk *) &data[datalen];
+
+ size = mxit_chunk_create_senddirect( chunk->data, username, filename, buf, buflen );
+ if ( size < 0 ) {
+ purple_debug_error( MXIT_PLUGIN_ID, "Error creating senddirect chunk (%i)\n", size );
+ return;
+ }
+
+ chunk->type = CP_CHUNK_DIRECT_SND;
+ chunk->length = htonl( size );
+ datalen += sizeof( struct raw_chunk ) + size;
+
+ /* send the byte stream to the mxit server */
+ mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a "reject file" multimedia packet.
+ *
+ * @param session The MXit session object
+ * @param fileid A unique ID that identifies this file
+ */
+void mxit_send_file_reject( struct MXitSession* session, const char* fileid )
+{
+ char data[CP_MAX_PACKET];
+ int datalen = 0;
+ struct raw_chunk* chunk;
+ int size;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_send_file_reject\n" );
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=" );
+
+ /* map chunk header over data buffer */
+ chunk = (struct raw_chunk *) &data[datalen];
+
+ size = mxit_chunk_create_reject( chunk->data, fileid );
+ if ( size < 0 ) {
+ purple_debug_error( MXIT_PLUGIN_ID, "Error creating reject chunk (%i)\n", size );
+ return;
+ }
+
+ chunk->type = CP_CHUNK_REJECT;
+ chunk->length = htonl( size );
+ datalen += sizeof( struct raw_chunk ) + size;
+
+ /* send the byte stream to the mxit server */
+ mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a "get file" multimedia packet.
+ *
+ * @param session The MXit session object
+ * @param fileid A unique ID that identifies this file
+ * @param filesize The number of bytes to retrieve
+ * @param offset Offset in file at which to start retrieving
+ */
+void mxit_send_file_accept( struct MXitSession* session, const char* fileid, int filesize, int offset )
+{
+ char data[CP_MAX_PACKET];
+ int datalen = 0;
+ struct raw_chunk* chunk;
+ int size;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_send_file_accept\n" );
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=" );
+
+ /* map chunk header over data buffer */
+ chunk = (struct raw_chunk *) &data[datalen];
+
+ size = mxit_chunk_create_get( chunk->data, fileid, filesize, offset );
+ if ( size < 0 ) {
+ purple_debug_error( MXIT_PLUGIN_ID, "Error creating getfile chunk (%i)\n", size );
+ return;
+ }
+
+ chunk->type = CP_CHUNK_GET;
+ chunk->length = htonl( size );
+ datalen += sizeof( struct raw_chunk ) + size;
+
+ /* send the byte stream to the mxit server */
+ mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a "received file" multimedia packet.
+ *
+ * @param session The MXit session object
+ * @param status The status of the file-transfer
+ */
+void mxit_send_file_received( struct MXitSession* session, const char* fileid, short status )
+{
+ char data[CP_MAX_PACKET];
+ int datalen = 0;
+ struct raw_chunk* chunk;
+ int size;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_send_file_received\n" );
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=" );
+
+ /* map chunk header over data buffer */
+ chunk = (struct raw_chunk *) &data[datalen];
+
+ size = mxit_chunk_create_received( chunk->data, fileid, status );
+ if ( size < 0 ) {
+ purple_debug_error( MXIT_PLUGIN_ID, "Error creating received chunk (%i)\n", size );
+ return;
+ }
+
+ chunk->type = CP_CHUNK_RECIEVED;
+ chunk->length = htonl( size );
+ datalen += sizeof( struct raw_chunk ) + size;
+
+ /* send the byte stream to the mxit server */
+ mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a "set avatar" multimedia packet.
+ *
+ * @param session The MXit session object
+ * @param data The avatar data
+ * @param buflen The length of the avatar data
+ */
+void mxit_set_avatar( struct MXitSession* session, const unsigned char* avatar, int avatarlen )
+{
+ char data[CP_MAX_PACKET];
+ int datalen = 0;
+ struct raw_chunk* chunk;
+ int size;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_set_avatar: %i bytes\n", avatarlen );
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=" );
+
+ /* map chunk header over data buffer */
+ chunk = (struct raw_chunk *) &data[datalen];
+
+ size = mxit_chunk_create_set_avatar( chunk->data, avatar, avatarlen );
+ if ( size < 0 ) {
+ purple_debug_error( MXIT_PLUGIN_ID, "Error creating set avatar chunk (%i)\n", size );
+ return;
+ }
+
+ chunk->type = CP_CHUNK_SET_AVATAR;
+ chunk->length = htonl( size );
+ datalen += sizeof( struct raw_chunk ) + size;
+
+ /* send the byte stream to the mxit server */
+ mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a "get avatar" multimedia packet.
+ *
+ * @param session The MXit session object
+ * @param mxitId The username who's avatar to request
+ * @param avatarId The id of the avatar image (as string)
+ * @param data The avatar data
+ * @param buflen The length of the avatar data
+ */
+void mxit_get_avatar( struct MXitSession* session, const char* mxitId, const char* avatarId )
+{
+ char data[CP_MAX_PACKET];
+ int datalen = 0;
+ struct raw_chunk* chunk;
+ int size;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_get_avatar: %s\n", mxitId );
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=" );
+
+ /* map chunk header over data buffer */
+ chunk = (struct raw_chunk *) &data[datalen];
+
+ size = mxit_chunk_create_get_avatar( chunk->data, mxitId, avatarId, MXIT_AVATAR_SIZE );
+ if ( size < 0 ) {
+ purple_debug_error( MXIT_PLUGIN_ID, "Error creating get avatar chunk (%i)\n", size );
+ return;
+ }
+
+ chunk->type = CP_CHUNK_GET_AVATAR;
+ chunk->length = htonl( size );
+ datalen += sizeof( struct raw_chunk ) + size;
+
+ /* send the byte stream to the mxit server */
+ mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA );
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a login message packet.
+ *
+ * @param session The MXit session object
+ * @param records The packet's data records
+ * @param rcount The number of data records
+ */
+static void mxit_parse_cmd_login( struct MXitSession* session, struct record** records, int rcount )
+{
+ PurpleStatus* status;
+ int presence;
+ const char* profilelist[] = { CP_PROFILE_BIRTHDATE, CP_PROFILE_GENDER, CP_PROFILE_HIDENUMBER, CP_PROFILE_FULLNAME,
+ CP_PROFILE_TITLE, CP_PROFILE_FIRSTNAME, CP_PROFILE_LASTNAME, CP_PROFILE_EMAIL,
+ CP_PROFILE_MOBILENR };
+
+ purple_account_set_int( session->acc, MXIT_CONFIG_STATE, MXIT_STATE_LOGIN );
+
+ /* we were not yet logged in so we need to complete the login sequence here */
+ session->flags |= MXIT_FLAG_LOGGEDIN;
+ purple_connection_update_progress( session->con, _( "Successfully Logged In..." ), 3, 4 );
+ purple_connection_set_state( session->con, PURPLE_CONNECTED );
+
+ /* display the current splash-screen */
+ if ( splash_popup_enabled( session ) )
+ splash_display( session );
+
+ /* update presence status */
+ status = purple_account_get_active_status( session->acc );
+ presence = mxit_convert_presence( purple_status_get_id( status ) );
+ if ( presence != MXIT_PRESENCE_ONLINE ) {
+ /* when logging into MXit, your default presence is online. but with the UI, one can change
+ * the presence to whatever. in the case where its changed to a different presence setting
+ * we need to send an update to the server, otherwise the user's presence will be out of
+ * sync between the UI and MXit.
+ */
+ mxit_send_presence( session, presence, purple_status_get_attr_string( status, "message" ) );
+ }
+
+ /* save extra info if this is a HTTP connection */
+ if ( session->http ) {
+ /* save the http server to use for this session */
+ g_strlcpy( session->http_server, records[1]->fields[3]->data, sizeof( session->http_server ) );
+
+ /* save the session id */
+ session->http_sesid = atoi( records[0]->fields[0]->data );
+ }
+
+ /* retrieve our MXit profile */
+ mxit_send_extprofile_request( session, NULL, ARRAY_SIZE( profilelist ), profilelist );
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a received message packet.
+ *
+ * @param session The MXit session object
+ * @param records The packet's data records
+ * @param rcount The number of data records
+ */
+static void mxit_parse_cmd_message( struct MXitSession* session, struct record** records, int rcount )
+{
+ struct RXMsgData* mx = NULL;
+ char* message = NULL;
+ int msglen = 0;
+ int msgflags = 0;
+ int msgtype = 0;
+
+ if ( ( rcount == 1 ) || ( records[0]->fcount < 2 ) || ( records[1]->fcount == 0 ) || ( records[1]->fields[0]->len == 0 ) ) {
+ /* packet contains no message or an empty message */
+ return;
+ }
+
+ message = records[1]->fields[0]->data;
+ msglen = strlen( message );
+
+ /* strip off dummy domain */
+ mxit_strip_domain( records[0]->fields[0]->data );
+
+#ifdef DEBUG_PROTOCOL
+ purple_debug_info( MXIT_PLUGIN_ID, "Message received from '%s'\n", records[0]->fields[0]->data );
+#endif
+
+ /* decode message flags (if any) */
+ if ( records[0]->fcount >= 5 )
+ msgflags = atoi( records[0]->fields[4]->data );
+ msgtype = atoi( records[0]->fields[2]->data );
+
+ if ( msgflags & CP_MSG_ENCRYPTED ) {
+ /* this is an encrypted message. we do not currently support those so ignore it */
+ PurpleBuddy* buddy;
+ const char* name;
+ char msg[128];
+
+ buddy = purple_find_buddy( session->acc, records[0]->fields[0]->data );
+ if ( buddy )
+ name = purple_buddy_get_alias( buddy );
+ else
+ name = records[0]->fields[0]->data;
+ g_snprintf( msg, sizeof( msg ), "%s sent you an encrypted message, but it is not supported on this client.", name );
+ mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Message Error" ), _( msg ) );
+ return;
+ }
+
+ /* create and initialise new markup struct */
+ mx = g_new0( struct RXMsgData, 1 );
+ mx->msg = g_string_sized_new( msglen );
+ mx->session = session;
+ mx->from = g_strdup( records[0]->fields[0]->data );
+ mx->timestamp = atoi( records[0]->fields[1]->data );
+ mx->got_img = FALSE;
+ mx->chatid = -1;
+ mx->img_count = 0;
+
+ /* update list of active chats */
+ if ( !find_active_chat( session->active_chats, mx->from ) ) {
+ session->active_chats = g_list_append( session->active_chats, g_strdup( mx->from ) );
+ }
+
+ if ( is_multimx_contact( session, mx->from ) ) {
+ /* this is a MultiMx chatroom message */
+ multimx_message_received( mx, message, msglen, msgtype, msgflags );
+ }
+ else {
+ mxit_parse_markup( mx, message, msglen, msgtype, msgflags );
+ }
+
+ /* we are now done parsing the message */
+ mx->converted = TRUE;
+ if ( mx->img_count == 0 ) {
+ /* we have all the data we need for this message to be displayed now. */
+ mxit_show_message( mx );
+ }
+ else {
+ /* this means there are still images outstanding for this message and
+ * still need to wait for them before we can display the message.
+ * so the image received callback function will eventually display
+ * the message. */
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a received subscription request packet.
+ *
+ * @param session The MXit session object
+ * @param records The packet's data records
+ * @param rcount The number of data records
+ */
+static void mxit_parse_cmd_new_sub( struct MXitSession* session, struct record** records, int rcount )
+{
+ struct contact* contact;
+ struct record* rec;
+ int i;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_new_sub (%i recs)\n", rcount );
+
+ for ( i = 0; i < rcount; i++ ) {
+ rec = records[i];
+
+ if ( rec->fcount < 4 ) {
+ purple_debug_error( MXIT_PLUGIN_ID, "BAD SUBSCRIPTION RECORD! %i fields\n", rec->fcount );
+ break;
+ }
+
+ /* build up a new contact info struct */
+ contact = g_new0( struct contact, 1 );
+
+ strcpy( contact->username, rec->fields[0]->data );
+ mxit_strip_domain( contact->username ); /* remove dummy domain */
+ strcpy( contact->alias, rec->fields[1]->data );
+ contact->type = atoi( rec->fields[2]->data );
+
+ if ( rec->fcount >= 5 ) {
+ /* there is a personal invite message attached */
+ contact->msg = strdup( rec->fields[4]->data );
+ }
+ else
+ contact->msg = NULL;
+
+ /* handle the subscription */
+ if ( contact-> type == MXIT_TYPE_MULTIMX ) { /* subscription to a MultiMX room */
+ char* creator = NULL;
+
+ if ( rec->fcount >= 6 )
+ creator = rec->fields[5]->data;
+
+ multimx_invite( session, contact, creator );
+ }
+ else
+ mxit_new_subscription( session, contact );
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a received contact update packet.
+ *
+ * @param session The MXit session object
+ * @param records The packet's data records
+ * @param rcount The number of data records
+ */
+static void mxit_parse_cmd_contact( struct MXitSession* session, struct record** records, int rcount )
+{
+ struct contact* contact = NULL;
+ struct record* rec;
+ int i;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_contact (%i recs)\n", rcount );
+
+ for ( i = 0; i < rcount; i++ ) {
+ rec = records[i];
+
+ if ( rec->fcount < 6 ) {
+ purple_debug_error( MXIT_PLUGIN_ID, "BAD CONTACT RECORD! %i fields\n", rec->fcount );
+ break;
+ }
+
+ /* build up a new contact info struct */
+ contact = g_new0( struct contact, 1 );
+
+ strcpy( contact->groupname, rec->fields[0]->data );
+ strcpy( contact->username, rec->fields[1]->data );
+ mxit_strip_domain( contact->username ); /* remove dummy domain */
+ strcpy( contact->alias, rec->fields[2]->data );
+
+ contact->presence = atoi( rec->fields[3]->data );
+ contact->type = atoi( rec->fields[4]->data );
+ contact->mood = atoi( rec->fields[5]->data );
+
+ if ( rec->fcount > 6 ) {
+ /* added in protocol 5.9.0 - flags & subtype */
+ contact->flags = atoi( rec->fields[6]->data );
+ contact->subtype = rec->fields[7]->data[0];
+ }
+
+ /* add the contact to the buddy list */
+ if ( contact-> type == MXIT_TYPE_MULTIMX ) /* contact is a MultiMX room */
+ multimx_created( session, contact );
+ else
+ mxit_update_contact( session, contact );
+ }
+
+ if ( !( session->flags & MXIT_FLAG_FIRSTROSTER ) ) {
+ session->flags |= MXIT_FLAG_FIRSTROSTER;
+ mxit_update_blist( session );
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a received presence update packet.
+ *
+ * @param session The MXit session object
+ * @param records The packet's data records
+ * @param rcount The number of data records
+ */
+static void mxit_parse_cmd_presence( struct MXitSession* session, struct record** records, int rcount )
+{
+ struct record* rec;
+ int i;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_presence (%i recs)\n", rcount );
+
+ for ( i = 0; i < rcount; i++ ) {
+ rec = records[i];
+
+ if ( rec->fcount < 6 ) {
+ purple_debug_error( MXIT_PLUGIN_ID, "BAD PRESENCE RECORD! %i fields\n", rec->fcount );
+ break;
+ }
+
+ /*
+ * The format of the record is:
+ * contactAddressN\1presenceN\1\moodN\1customMoodN\1statusMsgN\1avatarIdN
+ */
+ mxit_strip_domain( rec->fields[0]->data ); /* contactAddress */
+
+ mxit_update_buddy_presence( session, rec->fields[0]->data, atoi( rec->fields[1]->data ), atoi( rec->fields[2]->data ),
+ rec->fields[3]->data, rec->fields[4]->data, rec->fields[5]->data );
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a received extended profile packet.
+ *
+ * @param session The MXit session object
+ * @param records The packet's data records
+ * @param rcount The number of data records
+ */
+static void mxit_parse_cmd_extprofile( struct MXitSession* session, struct record** records, int rcount )
+{
+ const char* mxitId = records[0]->fields[0]->data;
+ struct MXitProfile* profile = NULL;
+ int count;
+ int i;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_extprofile: profile for '%s'\n", mxitId );
+
+ profile = g_new0( struct MXitProfile, 1 );
+
+ /* set the count for attributes */
+ count = atoi( records[0]->fields[1]->data );
+
+ for ( i = 0; i < count; i++ ) {
+ char* fname;
+ char* fvalue;
+ char* fstatus;
+ int f = ( i * 3 ) + 2;
+
+ fname = records[0]->fields[f]->data; /* field name */
+ fvalue = records[0]->fields[f + 1]->data; /* field value */
+ fstatus = records[0]->fields[f + 2]->data; /* field status */
+
+ /* first check the status on the returned attribute */
+ if ( fstatus[0] != '0' ) {
+ /* error: attribute requested was NOT found */
+ purple_debug_error( MXIT_PLUGIN_ID, "Bad profile status on attribute '%s' \n", fname );
+ continue;
+ }
+
+ if ( strcmp( CP_PROFILE_BIRTHDATE, fname ) == 0 ) {
+ /* birthdate */
+ if ( records[0]->fields[f + 1]->len > 10 ) {
+ fvalue[10] = '\0';
+ records[0]->fields[f + 1]->len = 10;
+ }
+ memcpy( profile->birthday, fvalue, records[0]->fields[f + 1]->len );
+ }
+ else if ( strcmp( CP_PROFILE_GENDER, fname ) == 0 ) {
+ /* gender */
+ profile->male = ( fvalue[0] == '1' );
+ }
+ else if ( strcmp( CP_PROFILE_HIDENUMBER, fname ) == 0 ) {
+ /* hide number */
+ profile->hidden = ( fvalue[0] == '1' );
+ }
+ else if ( strcmp( CP_PROFILE_FULLNAME, fname ) == 0 ) {
+ /* nickname */
+ g_strlcpy( profile->nickname, fvalue, sizeof( profile->nickname ) );
+ }
+ else if ( strcmp( CP_PROFILE_AVATAR, fname ) == 0 ) {
+ /* avatar id, we just ingore it cause we dont need it */
+ }
+ else if ( strcmp( CP_PROFILE_TITLE, fname ) == 0 ) {
+ /* title */
+ g_strlcpy( profile->title, fvalue, sizeof( profile->title ) );
+ }
+ else if ( strcmp( CP_PROFILE_FIRSTNAME, fname ) == 0 ) {
+ /* first name */
+ g_strlcpy( profile->firstname, fvalue, sizeof( profile->firstname ) );
+ }
+ else if ( strcmp( CP_PROFILE_LASTNAME, fname ) == 0 ) {
+ /* last name */
+ g_strlcpy( profile->lastname, fvalue, sizeof( profile->lastname ) );
+ }
+ else if ( strcmp( CP_PROFILE_EMAIL, fname ) == 0 ) {
+ /* email address */
+ g_strlcpy( profile->email, fvalue, sizeof( profile->email ) );
+ }
+ else if ( strcmp( CP_PROFILE_MOBILENR, fname ) == 0 ) {
+ /* mobile number */
+ g_strlcpy( profile->mobilenr, fvalue, sizeof( profile->mobilenr ) );
+ }
+ else {
+ /* invalid profile attribute */
+ purple_debug_error( MXIT_PLUGIN_ID, "Invalid profile attribute received '%s' \n", fname );
+ }
+ }
+
+ if ( records[0]->fields[0]->len == 0 ) {
+ /* no MXit id provided, so this must be our own profile information */
+ if ( session->profile )
+ g_free( session->profile );
+ session->profile = profile;
+ }
+ else {
+ /* display other user's profile */
+ mxit_show_profile( session, mxitId, profile );
+
+ /* cleanup */
+ g_free( profile );
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Return the length of a multimedia chunk
+ *
+ * @return The actual chunk data length in bytes
+ */
+static int get_chunk_len( const char* chunkdata )
+{
+ int* sizeptr;
+
+ sizeptr = (int*) &chunkdata[1]; /* we skip the first byte (type field) */
+
+ return ntohl( *sizeptr );
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a received multimedia packet.
+ *
+ * @param session The MXit session object
+ * @param records The packet's data records
+ * @param rcount The number of data records
+ */
+static void mxit_parse_cmd_media( struct MXitSession* session, struct record** records, int rcount )
+{
+ char type;
+ int size;
+
+ type = records[0]->fields[0]->data[0];
+ size = get_chunk_len( records[0]->fields[0]->data );
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_media (%i records) (%i bytes)\n", rcount, size );
+
+ /* supported chunked data types */
+ switch ( type ) {
+ case CP_CHUNK_CUSTOM : /* custom resource */
+ {
+ struct cr_chunk chunk;
+
+ /* decode the chunked data */
+ memset( &chunk, 0, sizeof( struct cr_chunk ) );
+ mxit_chunk_parse_cr( &records[0]->fields[0]->data[sizeof( char ) + sizeof( int )], records[0]->fields[0]->len, &chunk );
+
+ purple_debug_info( MXIT_PLUGIN_ID, "chunk info id=%s handle=%s op=%i\n", chunk.id, chunk.handle, chunk.operation );
+
+ /* this is a splash-screen operation */
+ if ( strcmp( chunk.handle, HANDLE_SPLASH2 ) == 0 ) {
+ if ( chunk.operation == CR_OP_UPDATE ) { /* update the splash-screen */
+ struct splash_chunk *splash = chunk.resources->data; // TODO: Fix - assuming 1st resource is splash
+ gboolean clickable = ( g_list_length( chunk.resources ) > 1 ); // TODO: Fix - if 2 resources, then is clickable
+
+ if ( splash != NULL )
+ splash_update( session, chunk.id, splash->data, splash->datalen, clickable );
+ }
+ else if ( chunk.operation == CR_OP_REMOVE ) /* remove the splash-screen */
+ splash_remove( session );
+ }
+
+ /* cleanup custom resources */
+ g_list_foreach( chunk.resources, (GFunc)g_free, NULL );
+
+ }
+ break;
+
+ case CP_CHUNK_OFFER : /* file offer */
+ {
+ struct offerfile_chunk chunk;
+
+ /* decode the chunked data */
+ memset( &chunk, 0, sizeof( struct offerfile_chunk ) );
+ mxit_chunk_parse_offer( &records[0]->fields[0]->data[sizeof( char ) + sizeof( int )], records[0]->fields[0]->len, &chunk );
+
+ /* process the offer */
+ mxit_xfer_rx_offer( session, chunk.username, chunk.filename, chunk.filesize, chunk.fileid );
+ }
+ break;
+
+ case CP_CHUNK_GET : /* get file response */
+ {
+ struct getfile_chunk chunk;
+
+ /* decode the chunked data */
+ memset( &chunk, 0, sizeof( struct getfile_chunk ) );
+ mxit_chunk_parse_get( &records[0]->fields[0]->data[sizeof( char ) + sizeof( int )], records[0]->fields[0]->len, &chunk );
+
+ /* process the getfile */
+ mxit_xfer_rx_file( session, chunk.fileid, chunk.data, chunk.length );
+ }
+ break;
+
+ case CP_CHUNK_GET_AVATAR : /* get avatars */
+ {
+ struct getavatar_chunk chunk;
+
+ /* decode the chunked data */
+ memset( &chunk, 0, sizeof ( struct getavatar_chunk ) );
+ mxit_chunk_parse_get_avatar( &records[0]->fields[0]->data[sizeof( char ) + sizeof( int )], records[0]->fields[0]->len, &chunk );
+
+ /* update avatar image */
+ if ( chunk.data ) {
+ purple_debug_info( MXIT_PLUGIN_ID, "updating avatar for contact '%s'\n", chunk.mxitid );
+ purple_buddy_icons_set_for_user( session->acc, chunk.mxitid, g_memdup( chunk.data, chunk.length), chunk.length, chunk.avatarid );
+ }
+
+ }
+ break;
+
+ case CP_CHUNK_SET_AVATAR :
+ /* this is a reply packet to a set avatar request. no action is required */
+ break;
+
+ case CP_CHUNK_DIRECT_SND :
+ /* this is a ack for a file send. no action is required */
+ break;
+
+ case CP_CHUNK_RECIEVED :
+ /* this is a ack for a file received. no action is required */
+ break;
+
+ default :
+ purple_debug_error( MXIT_PLUGIN_ID, "Unsupported chunked data packet type received (%i)\n", type );
+ break;
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Handle a redirect sent from the MXit server.
+ *
+ * @param session The MXit session object
+ * @param url The redirect information
+ */
+static void mxit_perform_redirect( struct MXitSession* session, const char* url )
+{
+ gchar** parts;
+ gchar** host;
+ int type;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_perform_redirect: %s\n", url );
+
+ /* tokenize the URL string */
+ parts = g_strsplit( url, ";", 0 );
+
+ /* Part 1: protocol://host:port */
+ host = g_strsplit( parts[0], ":", 4 );
+ if ( strcmp( host[0], "socket" ) == 0 ) {
+ /* redirect to a MXit socket proxy */
+ g_strlcpy( session->server, &host[1][2], sizeof( session->server ) );
+ session->port = atoi( host[2] );
+ }
+ else {
+ purple_connection_error( session->con, _( "Cannot perform redirect using the specified protocol" ) );
+ goto redirect_fail;
+ }
+
+ /* Part 2: type of redirect */
+ type = atoi( parts[1] );
+ if ( type == CP_REDIRECT_PERMANENT ) {
+ /* permanent redirect, so save new MXit server and port */
+ purple_account_set_string( session->acc, MXIT_CONFIG_SERVER_ADDR, session->server );
+ purple_account_set_int( session->acc, MXIT_CONFIG_SERVER_PORT, session->port );
+ }
+
+ /* Part 3: message (optional) */
+ if ( parts[2] != NULL )
+ purple_connection_notice( session->con, parts[2] );
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_perform_redirect: %s redirect to %s:%i\n",
+ ( type == CP_REDIRECT_PERMANENT ) ? "Permanent" : "Temporary", session->server, session->port );
+
+ /* perform the re-connect to the new MXit server */
+ mxit_reconnect( session );
+
+redirect_fail:
+ g_strfreev( parts );
+ g_strfreev( host );
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a success response received from the MXit server.
+ *
+ * @param session The MXit session object
+ * @param packet The received packet
+ */
+static int process_success_response( struct MXitSession* session, struct rx_packet* packet )
+{
+ /* ignore ping/poll packets */
+ if ( ( packet->cmd != CP_CMD_PING ) && ( packet->cmd != CP_CMD_POLL ) )
+ session->last_rx = time( NULL );
+
+ /*
+ * when we pass the packet records to the next level for parsing
+ * we minus 3 records because 1) the first record is the packet
+ * type 2) packet reply status 3) the last record is bogus
+ */
+
+ /* packet command */
+ switch ( packet->cmd ) {
+
+ case CP_CMD_REGISTER :
+ /* fall through, when registeration successful, MXit will auto login */
+ case CP_CMD_LOGIN :
+ /* login response */
+ if ( !( session->flags & MXIT_FLAG_LOGGEDIN ) ) {
+ mxit_parse_cmd_login( session, &packet->records[2], packet->rcount - 3 );
+ }
+ break;
+
+ case CP_CMD_LOGOUT :
+ /* logout response */
+ session->flags &= ~MXIT_FLAG_LOGGEDIN;
+ purple_account_disconnect( session->acc );
+
+ /* note:
+ * we do not prompt the user here for a reconnect, because this could be the user
+ * logging in with his phone. so we just disconnect the account otherwise
+ * mxit will start to bounce between the phone and pidgin. also could be a valid
+ * disconnect selected by the user.
+ */
+ return -1;
+
+ case CP_CMD_CONTACT :
+ /* contact update */
+ mxit_parse_cmd_contact( session, &packet->records[2], packet->rcount - 3 );
+ break;
+
+ case CP_CMD_PRESENCE :
+ /* presence update */
+ mxit_parse_cmd_presence(session, &packet->records[2], packet->rcount - 3 );
+ break;
+
+ case CP_CMD_RX_MSG :
+ /* incoming message (no bogus record) */
+ mxit_parse_cmd_message( session, &packet->records[2], packet->rcount - 2 );
+ break;
+
+ case CP_CMD_NEW_SUB :
+ /* new subscription request */
+ mxit_parse_cmd_new_sub( session, &packet->records[2], packet->rcount - 3 );
+ break;
+
+ case CP_CMD_MEDIA :
+ /* multi-media message */
+ mxit_parse_cmd_media( session, &packet->records[2], packet->rcount - 2 );
+ break;
+
+ case CP_CMD_EXTPROFILE_GET :
+ /* profile update */
+ mxit_parse_cmd_extprofile( session, &packet->records[2], packet->rcount - 2 );
+ break;
+
+ case CP_CMD_MOOD :
+ /* mood update */
+ case CP_CMD_UPDATE :
+ /* update contact information */
+ case CP_CMD_ALLOW :
+ /* allow subscription ack */
+ case CP_CMD_DENY :
+ /* deny subscription ack */
+ case CP_CMD_INVITE :
+ /* invite contact ack */
+ case CP_CMD_REMOVE :
+ /* remove contact ack */
+ case CP_CMD_TX_MSG :
+ /* outgoing message ack */
+ case CP_CMD_STATUS :
+ /* presence update ack */
+ case CP_CMD_GRPCHAT_CREATE :
+ /* create groupchat */
+ case CP_CMD_GRPCHAT_INVITE :
+ /* groupchat invite */
+ case CP_CMD_PING :
+ /* ping reply */
+ case CP_CMD_POLL :
+ /* HTTP poll reply */
+ case CP_CMD_EXTPROFILE_SET :
+ /* profile update */
+ case CP_CMD_SPLASHCLICK :
+ /* splash-screen clickthrough */
+ break;
+
+ default :
+ /* unknown packet */
+ purple_debug_error( MXIT_PLUGIN_ID, "Received unknown client packet (cmd = %i)\n", packet->cmd );
+ }
+
+ return 0;
+}
+
+
+/*------------------------------------------------------------------------
+ * Process an error response received from the MXit server.
+ *
+ * @param session The MXit session object
+ * @param packet The received packet
+ */
+static int process_error_response( struct MXitSession* session, struct rx_packet* packet )
+{
+ char errmsg[256];
+ const char* errdesc;
+
+ /* set the error description to be shown to the user */
+ if ( packet->errmsg )
+ errdesc = packet->errmsg;
+ else
+ errdesc = "An internal MXit server error occurred.";
+
+ purple_debug_info( MXIT_PLUGIN_ID, "Error Reply %i:%s\n", packet->errcode, errdesc );
+
+ if ( packet->errcode == MXIT_ERRCODE_LOGGEDOUT ) {
+ /* we are not currently logged in, so we need to reconnect */
+ purple_connection_error( session->con, _( errmsg ) );
+ }
+
+ /* packet command */
+ switch ( packet->cmd ) {
+
+ case CP_CMD_REGISTER :
+ case CP_CMD_LOGIN :
+ if ( packet->errcode == MXIT_ERRCODE_REDIRECT ) {
+ mxit_perform_redirect( session, packet->errmsg );
+ return 0;
+ }
+ else {
+ sprintf( errmsg, "Login error: %s (%i)", errdesc, packet->errcode );
+ purple_connection_error( session->con, _( errmsg ) );
+ return -1;
+ }
+ case CP_CMD_LOGOUT :
+ sprintf( errmsg, "Logout error: %s (%i)", errdesc, packet->errcode );
+ purple_connection_error_reason( session->con, PURPLE_CONNECTION_ERROR_NAME_IN_USE, _( errmsg ) );
+ return -1;
+ case CP_CMD_CONTACT :
+ mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Contact Error" ), _( errdesc ) );
+ break;
+ case CP_CMD_RX_MSG :
+ mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Message Error" ), _( errdesc ) );
+ break;
+ case CP_CMD_TX_MSG :
+ mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Message Sending Error" ), _( errdesc ) );
+ break;
+ case CP_CMD_STATUS :
+ mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Status Error" ), _( errdesc ) );
+ break;
+ case CP_CMD_MOOD :
+ mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Mood Error" ), _( errdesc ) );
+ break;
+ case CP_CMD_KICK :
+ /*
+ * the MXit server sends this packet if we were idle for too long.
+ * to stop the server from closing this connection we need to resend
+ * the login packet.
+ */
+ mxit_send_login( session );
+ break;
+ case CP_CMD_INVITE :
+ mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Invitation Error" ), _( errdesc ) );
+ break;
+ case CP_CMD_REMOVE :
+ mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Contact Removal Error" ), _( errdesc ) );
+ break;
+ case CP_CMD_ALLOW :
+ case CP_CMD_DENY :
+ mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Subscription Error" ), _( errdesc ) );
+ break;
+ case CP_CMD_UPDATE :
+ mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Contact Update Error" ), _( errdesc ) );
+ break;
+ case CP_CMD_MEDIA :
+ mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "File Transfer Error" ), _( errdesc ) );
+ break;
+ case CP_CMD_GRPCHAT_CREATE :
+ mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Cannot create MultiMx room" ), _( errdesc ) );
+ break;
+ case CP_CMD_GRPCHAT_INVITE :
+ mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "MultiMx Invitation Error" ), _( errdesc ) );
+ break;
+ case CP_CMD_EXTPROFILE_GET :
+ case CP_CMD_EXTPROFILE_SET :
+ mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Profile Error" ), _( errdesc ) );
+ break;
+ case CP_CMD_SPLASHCLICK :
+ /* ignore error */
+ break;
+ case CP_CMD_PING :
+ case CP_CMD_POLL :
+ break;
+ default :
+ mxit_popup( PURPLE_NOTIFY_MSG_ERROR, _( "Error" ), _( errdesc ) );
+ break;
+ }
+
+ return 0;
+}
+
+
+/*========================================================================================================================
+ * Low-level Packet receive
+ */
+
+#ifdef DEBUG_PROTOCOL
+/*------------------------------------------------------------------------
+ * Dump a received packet structure.
+ *
+ * @param p The received packet
+ */
+static void dump_packet( struct rx_packet* p )
+{
+ struct record* r = NULL;
+ struct field* f = NULL;
+ int i;
+ int j;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "PACKET DUMP: (%i records)\n", p->rcount );
+
+ for ( i = 0; i < p->rcount; i++ ) {
+ r = p->records[i];
+ purple_debug_info( MXIT_PLUGIN_ID, "RECORD: (%i fields)\n", r->fcount );
+
+ for ( j = 0; j < r->fcount; j++ ) {
+ f = r->fields[j];
+ purple_debug_info( MXIT_PLUGIN_ID, "\tFIELD: (len=%i) '%s' \n", f->len, f->data );
+ }
+ }
+}
+#endif
+
+
+/*------------------------------------------------------------------------
+ * Free up memory used by a packet structure.
+ *
+ * @param p The received packet
+ */
+static void free_rx_packet( struct rx_packet* p )
+{
+ struct record* r = NULL;
+ struct field* f = NULL;
+ int i;
+ int j;
+
+ for ( i = 0; i < p->rcount; i++ ) {
+ r = p->records[i];
+
+ for ( j = 0; j < r->fcount; j++ ) {
+ g_free( f );
+ }
+ g_free( r->fields );
+ g_free( r );
+ }
+ g_free( p->records );
+}
+
+
+/*------------------------------------------------------------------------
+ * Add a new field to a record.
+ *
+ * @param r Parent record object
+ * @return The newly created field
+ */
+static struct field* add_field( struct record* r )
+{
+ struct field* field;
+
+ field = g_new0( struct field, 1 );
+
+ r->fields = realloc( r->fields, sizeof( struct field* ) * ( r->fcount + 1 ) );
+ r->fields[r->fcount] = field;
+ r->fcount++;
+
+ return field;
+}
+
+
+/*------------------------------------------------------------------------
+ * Add a new record to a packet.
+ *
+ * @param p The packet object
+ * @return The newly created record
+ */
+static struct record* add_record( struct rx_packet* p )
+{
+ struct record* rec;
+
+ rec = g_new0( struct record, 1 );
+
+ p->records = realloc( p->records, sizeof( struct record* ) * ( p->rcount + 1 ) );
+ p->records[p->rcount] = rec;
+ p->rcount++;
+
+ return rec;
+}
+
+
+/*------------------------------------------------------------------------
+ * Parse the received byte stream into a proper client protocol packet.
+ *
+ * @param session The MXit session object
+ * @return Success (0) or Failure (!0)
+ */
+int mxit_parse_packet( struct MXitSession* session )
+{
+ struct rx_packet packet;
+ struct record* rec;
+ struct field* field;
+ gboolean pbreak;
+ unsigned int i;
+ int res = 0;
+
+#ifdef DEBUG_PROTOCOL
+ purple_debug_info( MXIT_PLUGIN_ID, "Received packet (%i bytes)\n", session->rx_i );
+ dump_bytes( session, session->rx_dbuf, session->rx_i );
+#endif
+
+ i = 0;
+ while ( i < session->rx_i ) {
+
+ /* create first record and field */
+ rec = NULL;
+ field = NULL;
+ memset( &packet, 0x00, sizeof( struct rx_packet ) );
+ rec = add_record( &packet );
+ pbreak = FALSE;
+
+ /* break up the received packet into fields and records for easy parsing */
+ while ( ( i < session->rx_i ) && ( !pbreak ) ) {
+
+ switch ( session->rx_dbuf[i] ) {
+ case CP_SOCK_REC_TERM :
+ /* new record */
+ if ( packet.rcount == 1 ) {
+ /* packet command */
+ packet.cmd = atoi( packet.records[0]->fields[0]->data );
+ }
+ else if ( packet.rcount == 2 ) {
+ /* special case: binary multimedia packets should not be parsed here */
+ if ( packet.cmd == CP_CMD_MEDIA ) {
+ /* add the chunked to new record */
+ rec = add_record( &packet );
+ field = add_field( rec );
+ field->data = &session->rx_dbuf[i + 1];
+ field->len = session->rx_i - i;
+ /* now skip the binary data */
+ res = get_chunk_len( field->data );
+ /* determine if we have more packets */
+ if ( res + 6 + i < session->rx_i ) {
+ /* we have more than one packet in this stream */
+ i += res + 6;
+ pbreak = TRUE;
+ }
+ else {
+ i = session->rx_i;
+ }
+ }
+ }
+ else if ( !field ) {
+ field = add_field( rec );
+ field->data = &session->rx_dbuf[i];
+ }
+ session->rx_dbuf[i] = '\0';
+ rec = add_record( &packet );
+ field = NULL;
+
+ break;
+ case CP_FLD_TERM :
+ /* new field */
+ session->rx_dbuf[i] = '\0';
+ if ( !field ) {
+ field = add_field( rec );
+ field->data = &session->rx_dbuf[i];
+ }
+ field = NULL;
+ break;
+ case CP_PKT_TERM :
+ /* packet is done! */
+ session->rx_dbuf[i] = '\0';
+ pbreak = TRUE;
+ break;
+ default :
+ /* skip non special characters */
+ if ( !field ) {
+ field = add_field( rec );
+ field->data = &session->rx_dbuf[i];
+ }
+ field->len++;
+ break;
+ }
+
+ i++;
+ }
+
+ if ( packet.rcount < 2 ) {
+ /* bad packet */
+ purple_connection_error( session->con, _( "Invalid packet received from MXit." ) );
+ free_rx_packet( &packet );
+ continue;
+ }
+
+ session->rx_dbuf[session->rx_i] = '\0';
+ packet.errcode = atoi( packet.records[1]->fields[0]->data );
+
+ purple_debug_info( MXIT_PLUGIN_ID, "Packet received CMD:%i (%i)\n", packet.cmd, packet.errcode );
+#ifdef DEBUG_PROTOCOL
+ /* debug */
+ dump_packet( &packet );
+#endif
+
+ /* reset the out ack */
+ if ( session->outack == packet.cmd ) {
+ /* outstanding ack received from mxit server */
+ session->outack = 0;
+ }
+
+ /* check packet status */
+ if ( packet.errcode != MXIT_ERRCODE_SUCCESS ) {
+ /* error reply! */
+ if ( ( packet.records[1]->fcount > 1 ) && ( packet.records[1]->fields[1]->data ) )
+ packet.errmsg = packet.records[1]->fields[1]->data;
+ else
+ packet.errmsg = NULL;
+
+ res = process_error_response( session, &packet );
+ }
+ else {
+ /* success reply! */
+ res = process_success_response( session, &packet );
+ }
+
+ /* free up the packet resources */
+ free_rx_packet( &packet );
+ }
+
+ if ( session->outack == 0 )
+ mxit_manage_queue( session );
+
+ return res;
+}
+
+
+/*------------------------------------------------------------------------
+ * Callback when data is received from the MXit server.
+ *
+ * @param user_data The MXit session object
+ * @param source The file-descriptor on which data was received
+ * @param cond Condition which caused the callback (PURPLE_INPUT_READ)
+ */
+void mxit_cb_rx( gpointer user_data, gint source, PurpleInputCondition cond )
+{
+ struct MXitSession* session = (struct MXitSession*) user_data;
+ char ch;
+ int res;
+ int len;
+
+ if ( session->rx_state == RX_STATE_RLEN ) {
+ /* we are reading in the packet length */
+ len = read( session->fd, &ch, 1 );
+ if ( len < 0 ) {
+ /* connection error */
+ purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x01)" ) );
+ return;
+ }
+ else if ( len == 0 ) {
+ /* connection closed */
+ purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x02)" ) );
+ return;
+ }
+ else {
+ /* byte read */
+ if ( ch == CP_REC_TERM ) {
+ /* the end of the length record found */
+ session->rx_lbuf[session->rx_i] = '\0';
+ session->rx_res = atoi( &session->rx_lbuf[3] );
+ if ( session->rx_res > CP_MAX_PACKET ) {
+ purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x03)" ) );
+ }
+ session->rx_state = RX_STATE_DATA;
+ session->rx_i = 0;
+ }
+ else {
+ /* still part of the packet length record */
+ session->rx_lbuf[session->rx_i] = ch;
+ session->rx_i++;
+ if ( session->rx_i >= sizeof( session->rx_lbuf ) ) {
+ /* malformed packet length record (too long) */
+ purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x04)" ) );
+ return;
+ }
+ }
+ }
+ }
+ else if ( session->rx_state == RX_STATE_DATA ) {
+ /* we are reading in the packet data */
+ len = read( session->fd, &session->rx_dbuf[session->rx_i], session->rx_res );
+ if ( len < 0 ) {
+ /* connection error */
+ purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x05)" ) );
+ return;
+ }
+ else if ( len == 0 ) {
+ /* connection closed */
+ purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x06)" ) );
+ return;
+ }
+ else {
+ /* data read */
+ session->rx_i += len;
+ session->rx_res -= len;
+
+ if ( session->rx_res == 0 ) {
+ /* ok, so now we have read in the whole packet */
+ session->rx_state = RX_STATE_PROC;
+ }
+ }
+ }
+
+ if ( session->rx_state == RX_STATE_PROC ) {
+ /* we have a full packet, which we now need to process */
+ res = mxit_parse_packet( session );
+
+ if ( res == 0 ) {
+ /* we are still logged in */
+ session->rx_state = RX_STATE_RLEN;
+ session->rx_res = 0;
+ session->rx_i = 0;
+ }
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Log the user off MXit and close the connection
+ *
+ * @param session The MXit session object
+ */
+void mxit_close_connection( struct MXitSession* session )
+{
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_close_connection\n" );
+
+ if ( !( session->flags & MXIT_FLAG_CONNECTED ) ) {
+ /* we are already closed */
+ return;
+ }
+ else if ( session->flags & MXIT_FLAG_LOGGEDIN ) {
+ /* we are currently logged in so we need to send a logout packet */
+ if ( !session->http ) {
+ mxit_send_logout( session );
+ }
+ session->flags &= ~MXIT_FLAG_LOGGEDIN;
+ }
+ session->flags &= ~MXIT_FLAG_CONNECTED;
+
+ /* cancel outstanding HTTP request */
+ if ( ( session->http ) && ( session->http_out_req ) ) {
+ purple_util_fetch_url_cancel( (PurpleUtilFetchUrlData*) session->http_out_req );
+ session->http_out_req = NULL;
+ }
+
+ /* remove the input cb function */
+ if ( session->con->inpa ) {
+ purple_input_remove( session->con->inpa );
+ session->con->inpa = 0;
+ }
+
+ /* remove HTTP poll timer */
+ if ( session->http_timer_id > 0 )
+ purple_timeout_remove( session->http_timer_id );
+
+ /* remove queue manager timer */
+ if ( session->q_timer > 0 )
+ purple_timeout_remove( session->q_timer );
+
+ /* remove all groupchat rooms */
+ while ( session->rooms != NULL ) {
+ struct multimx* multimx = (struct multimx *) session->rooms->data;
+
+ session->rooms = g_list_remove( session->rooms, multimx );
+
+ free( multimx );
+ }
+ g_list_free( session->rooms );
+ session->rooms = NULL;
+
+ /* remove all rx chats names */
+ while ( session->active_chats != NULL ) {
+ char* chat = (char*) session->active_chats->data;
+
+ session->active_chats = g_list_remove( session->active_chats, chat );
+
+ g_free( chat );
+ }
+ g_list_free( session->active_chats );
+ session->active_chats = NULL;
+
+ /* free profile information */
+ if ( session->profile )
+ free( session->profile );
+
+ /* free custom emoticons */
+ mxit_free_emoticon_cache( session );
+
+ /* free allocated memory */
+ g_free( session->encpwd );
+ session->encpwd = NULL;
+
+ /* flush all the commands still in the queue */
+ flush_queue( session );
+}
+
diff --git a/libpurple/protocols/mxit/protocol.h b/libpurple/protocols/mxit/protocol.h
new file mode 100644
index 0000000000..679bd4bc4c
--- /dev/null
+++ b/libpurple/protocols/mxit/protocol.h
@@ -0,0 +1,304 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- MXit client protocol implementation --
+ *
+ * Pieter Loubser <libpurple@mxit.com>
+ *
+ * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
+ * <http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#ifndef _MXIT_PROTO_H_
+#define _MXIT_PROTO_H_
+
+
+/* Client protocol constants */
+#define CP_SOCK_REC_TERM '\x00' /* socket record terminator */
+#define CP_HTTP_REC_TERM '\x26' /* http record terminator '&' */
+#define CP_FLD_TERM '\x01' /* field terminator */
+#define CP_PKT_TERM '\x02' /* packet terminator */
+
+
+#define CP_MAX_PACKET ( 1024 * 1024 ) /* maximum client protocol packet size (1 MiB) */
+#define CP_MAX_FILESIZE ( 150 * 1000 ) /* maximum client protocol file transfer size (150 KB) */
+#define MXIT_EMOTICON_SIZE 18 /* icon size for custom emoticons */
+#define CP_MAX_STATUS_MSG 250 /* maximum status message length (in characters) */
+
+/* Avatars */
+#define MXIT_AVATAR_SIZE 96 /* default avatar image size 96x96 */
+#define MXIT_AVATAR_TYPE "PNG" /* request avatars in this file type (only a suggestion) */
+#define MXIT_AVATAR_BITDEPT 24 /* request avatars with this bit depth (only a suggestion) */
+
+/* Protocol error codes */
+#define MXIT_ERRCODE_SUCCESS 0
+#define MXIT_ERRCODE_REDIRECT 16
+#define MXIT_ERRCODE_LOGGEDOUT 42
+
+/* MXit client features */
+#define MXIT_CF_NONE 0x000000
+#define MXIT_CF_FORMS 0x000001
+#define MXIT_CF_FILE_TRANSFER 0x000002
+#define MXIT_CF_CAMERA 0x000004
+#define MXIT_CF_COMMANDS 0x000008
+#define MXIT_CF_SMS 0x000010
+#define MXIT_CF_FILE_ACCESS 0x000020
+#define MXIT_CF_MIDP2 0x000040
+#define MXIT_CF_SKINS 0x000080
+#define MXIT_CF_AUDIO 0x000100
+#define MXIT_CF_ENCRYPTION 0x000200
+#define MXIT_CF_VOICE_REC 0x000400
+#define MXIT_CF_VECTOR_GFX 0x000800
+#define MXIT_CF_IMAGES 0x001000
+#define MXIT_CF_MARKUP 0x002000
+#define MXIT_CF_VIBES 0x004000
+#define MXIT_CF_SELECT_CONTACT 0x008000
+#define MXIT_CF_CUSTOM_EMO 0x010000
+#define MXIT_CF_ALERT_PROFILES 0x020000
+#define MXIT_CF_EXT_MARKUP 0x040000
+#define MXIT_CF_PLAIN_PWD 0x080000
+#define MXIT_CF_NO_GATEWAYS 0x100000
+
+/* Client features supported by this implementation */
+#define MXIT_CP_FEATURES ( MXIT_CF_FILE_TRANSFER | MXIT_CF_FILE_ACCESS | MXIT_CF_AUDIO | MXIT_CF_MARKUP | MXIT_CF_EXT_MARKUP | MXIT_CF_NO_GATEWAYS | MXIT_CF_IMAGES | MXIT_CF_COMMANDS | MXIT_CF_VIBES | MXIT_CF_MIDP2 )
+
+
+#define MXIT_PING_INTERVAL ( 5 * 60 ) /* ping the server after X seconds of being idle (5 minutes) */
+#define MXIT_ACK_TIMEOUT ( 30 ) /* timeout after waiting X seconds for an ack from the server (30 seconds) */
+
+/* MXit client version */
+#define MXIT_CP_DISTCODE "P" /* client distribution code (magic, do not touch!) */
+#define MXIT_CP_RELEASE "5.9.0" /* client protocol release version supported */
+#define MXIT_CP_ARCH "Y" /* client architecture series (Y not for Yoda but for PC-client) */
+#define MXIT_CLIENT_ID "LP" /* client ID as specified by MXit */
+#define MXIT_CP_PLATFORM "PURPLE" /* client platform */
+#define MXIT_CP_VERSION MXIT_CP_DISTCODE"-"MXIT_CP_RELEASE"-"MXIT_CP_ARCH"-"MXIT_CP_PLATFORM
+
+/* set operating system name */
+#if defined( __APPLE__ )
+#define MXIT_CP_OS "apple"
+#elif defined( _WIN32 )
+#define MXIT_CP_OS "windows"
+#elif defined( __linux__ )
+#define MXIT_CP_OS "linux"
+#else
+#define MXIT_CP_OS "unknown"
+#endif
+
+/* Client capabilities */
+#define MXIT_CP_CAP "utf8=true;cid="MXIT_CLIENT_ID
+
+/* Client settings */
+#define MAX_QUEUE_SIZE ( 1 << 4 ) /* tx queue size (16 packets) */
+#define MXIT_POPUP_WIN_NAME "MXit Notification" /* popup window name */
+#define MXIT_MAX_ATTRIBS 10 /* maximum profile attributes supported */
+#define MXIT_DEFAULT_LOCALE "en" /* default locale setting */
+#define MXIT_DEFAULT_LOC "planetpurple" /* the default location for registration */
+
+/* Client protocol commands */
+#define CP_CMD_LOGIN 0x0001 /* (1) login */
+#define CP_CMD_LOGOUT 0x0002 /* (2) logout */
+#define CP_CMD_CONTACT 0x0003 /* (3) get contacts */
+#define CP_CMD_UPDATE 0x0005 /* (5) update contact information */
+#define CP_CMD_INVITE 0x0006 /* (6) subscribe to new contact */
+#define CP_CMD_PRESENCE 0x0007 /* (7) get presence */
+#define CP_CMD_REMOVE 0x0008 /* (8) remove contact */
+#define CP_CMD_RX_MSG 0x0009 /* (9) get new messages */
+#define CP_CMD_TX_MSG 0x000A /* (10) send new message */
+#define CP_CMD_REGISTER 0x000B /* (11) register */
+//#define CP_CMD_PROFILE_SET 0x000C /* (12) set profile (DEPRECATED see CP_CMD_EXTPROFILE_SET) */
+#define CP_CMD_POLL 0x0011 /* (17) poll the HTTP server for an update */
+//#define CP_CMD_PROFILE_GET 0x001A /* (26) get profile (DEPRECATED see CP_CMD_EXTPROFILE_GET) */
+#define CP_CMD_MEDIA 0x001B /* (27) get multimedia message */
+#define CP_CMD_SPLASHCLICK 0x001F /* (31) splash-screen clickthrough */
+#define CP_CMD_STATUS 0x0020 /* (32) set shown presence & status */
+#define CP_CMD_MOOD 0x0029 /* (41) set mood */
+#define CP_CMD_KICK 0x002B /* (43) login kick */
+#define CP_CMD_GRPCHAT_CREATE 0x002C /* (44) create new groupchat */
+#define CP_CMD_GRPCHAT_INVITE 0x002D /* (45) add new groupchat member */
+#define CP_CMD_NEW_SUB 0x0033 /* (51) get new subscription */
+#define CP_CMD_ALLOW 0x0034 /* (52) allow subscription */
+#define CP_CMD_DENY 0x0037 /* (55) deny subscription */
+#define CP_CMD_EXTPROFILE_GET 0x0039 /* (57) get extended profile */
+#define CP_CMD_EXTPROFILE_SET 0x003A /* (58) set extended profile */
+#define CP_CMD_PING 0x03E8 /* (1000) ping (keepalive) */
+
+/* HTTP connection */
+#define MXIT_HTTP_POLL_MIN 7 /* minimum time between HTTP polls (seconds) */
+#define MXIT_HTTP_POLL_MAX ( 10 * 60 ) /* maximum time between HTTP polls (seconds) */
+
+/* receiver states */
+#define RX_STATE_RLEN 0x01 /* reading packet length section */
+#define RX_STATE_DATA 0x02 /* reading packet data section */
+#define RX_STATE_PROC 0x03 /* process read data */
+
+/* message flags */
+#define CP_MSG_ENCRYPTED 0x0010 /* message is encrypted */
+#define CP_MSG_MARKUP 0x0200 /* message may contain markup */
+#define CP_MSG_EMOTICON 0x0400 /* message may contain custom emoticons */
+
+/* redirect types */
+#define CP_REDIRECT_PERMANENT 1 /* permanent redirect */
+#define CP_REDIRECT_TEMPORARY 2 /* temporary redirect */
+
+/* message tx types */
+#define CP_MSGTYPE_NORMAL 0x01 /* normal message */
+#define CP_MSGTYPE_CHAT 0x02 /* chat message */
+#define CP_MSGTYPE_HEADLINE 0x03 /* headline message */
+#define CP_MSGTYPE_ERROR 0x04 /* error message */
+#define CP_MSGTYPE_GROUPCHAT 0x05 /* groupchat message */
+#define CP_MSGTYPE_FORM 0x06 /* mxit custom form */
+#define CP_MSGTYPE_COMMAND 0x07 /* mxit command */
+
+
+/* extended profile attribute fields */
+#define CP_PROFILE_BIRTHDATE "birthdate" /* Birthdate (String - ISO 8601 format) */
+#define CP_PROFILE_GENDER "gender" /* Gender (Boolean - 0=female, 1=male) */
+#define CP_PROFILE_HIDENUMBER "hidenumber" /* Hide Number (Boolean - 0=false, 1=true) */
+#define CP_PROFILE_FULLNAME "fullname" /* Fullname (UTF8 String) */
+#define CP_PROFILE_STATUS "statusmsg" /* Status Message (UTF8 String) */
+#define CP_PROFILE_PREVSTATUS "prevstatusmsgs" /* Previous Status Messages (UTF8 String) */
+#define CP_PROFILE_AVATAR "avatarid" /* Avatar ID (String) */
+#define CP_PROFILE_MODIFIED "lastmodified" /* Last-Modified timestamp */
+#define CP_PROFILE_TITLE "title" /* Title (UTF8 String) */
+#define CP_PROFILE_FIRSTNAME "firstname" /* First name (UTF8 String) */
+#define CP_PROFILE_LASTNAME "lastname" /* Last name (UTF8 String) */
+#define CP_PROFILE_EMAIL "email" /* Email address (UTF8 String) */
+#define CP_PROFILE_MOBILENR "mobilenumber" /* Mobile Number (UTF8 String) */
+
+/* extended profile field types */
+#define CP_PROF_TYPE_BOOL 0x02 /* boolean profile attribute type */
+#define CP_PROF_TYPE_INT 0x05 /* integer profile attribute type */
+#define CP_PROF_TYPE_UTF8 0x0A /* UTF8 string profile attribute type */
+#define CP_PROF_TYPE_DATE 0x0B /* date-time profile attribute type */
+
+
+/* define this to enable protocol debugging (very verbose logging) */
+#define DEBUG_PROTOCOL
+
+
+/* ======================================================================================= */
+
+struct MXitSession;
+
+/*------------------------------------------*/
+
+struct field {
+ char* data;
+ int len;
+};
+
+struct record {
+ struct field** fields;
+ int fcount;
+};
+
+struct rx_packet {
+ int cmd;
+ int errcode;
+ char* errmsg;
+ struct record** records;
+ int rcount;
+};
+
+struct tx_packet {
+ int cmd;
+ char header[256];
+ int headerlen;
+ char* data;
+ int datalen;
+};
+
+/*------------------------------------------*/
+
+
+/*
+ * A received message data object
+ */
+struct RXMsgData {
+ struct MXitSession* session; /* MXit session object */
+ char* from; /* the sender's name */
+ time_t timestamp; /* time at which the message was sent */
+ GString* msg; /* newly created message converted to libPurple formatting */
+ gboolean got_img; /* flag to say if this message got any images/emoticons embedded */
+ short img_count; /* the amount of images/emoticons still outstanding for the message */
+ int chatid; /* multimx chatroom id */
+ int flags; /* libPurple conversation flags */
+ gboolean converted; /* true if the message has been completely parsed and converted to libPurple markup */
+ gboolean processed; /* the message has been processed completely and should be freed up */
+};
+
+
+
+/*
+ * The packet transmission queue.
+ */
+struct tx_queue {
+ struct tx_packet* packets[MAX_QUEUE_SIZE]; /* array of packet pointers */
+ int count; /* number of packets queued */
+ int rd_i; /* queue current read index (queue offset for reading a packet) */
+ int wr_i; /* queue current write index (queue offset for adding new packet) */
+};
+
+
+/* ======================================================================================= */
+
+void mxit_popup( int type, const char* heading, const char* message );
+void mxit_strip_domain( char* username );
+gboolean find_active_chat( const GList* chats, const char* who );
+
+void mxit_cb_rx( gpointer data, gint source, PurpleInputCondition cond );
+gboolean mxit_manage_queue( gpointer user_data );
+gboolean mxit_manage_polling( gpointer user_data );
+
+void mxit_send_register( struct MXitSession* session );
+void mxit_send_login( struct MXitSession* session );
+void mxit_send_logout( struct MXitSession* session );
+void mxit_send_ping( struct MXitSession* session );
+void mxit_send_poll( struct MXitSession* session );
+
+void mxit_send_presence( struct MXitSession* session, int presence, const char* statusmsg );
+void mxit_send_mood( struct MXitSession* session, int mood );
+void mxit_send_message( struct MXitSession* session, const char* to, const char* msg, gboolean parse_markup );
+
+void mxit_send_extprofile_update( struct MXitSession* session, const char* password, unsigned int nr_attrib, const char* attributes );
+void mxit_send_extprofile_request( struct MXitSession* session, const char* username, unsigned int nr_attrib, const char* attribute[] );
+
+void mxit_send_invite( struct MXitSession* session, const char* username, const char* alias, const char* groupname );
+void mxit_send_remove( struct MXitSession* session, const char* username );
+void mxit_send_allow_sub( struct MXitSession* session, const char* username, const char* alias );
+void mxit_send_deny_sub( struct MXitSession* session, const char* username );
+void mxit_send_update_contact( struct MXitSession* session, const char* username, const char* alias, const char* groupname );
+void mxit_send_splashclick( struct MXitSession* session, const char* splashid );
+
+void mxit_send_file( struct MXitSession* session, const char* username, const char* filename, const unsigned char* buf, int buflen );
+void mxit_send_file_reject( struct MXitSession* session, const char* fileid );
+void mxit_send_file_accept( struct MXitSession* session, const char* fileid, int filesize, int offset );
+void mxit_send_file_received( struct MXitSession* session, const char* fileid, short status );
+void mxit_set_avatar( struct MXitSession* session, const unsigned char* avatar, int avatarlen );
+void mxit_get_avatar( struct MXitSession* session, const char* mxitId, const char* avatarId );
+
+void mxit_send_groupchat_create( struct MXitSession* session, const char* groupname, int nr_usernames, const char* usernames[] );
+void mxit_send_groupchat_invite( struct MXitSession* session, const char* roomid, int nr_usernames, const char* usernames[] );
+
+int mxit_parse_packet( struct MXitSession* session );
+void dump_bytes( struct MXitSession* session, const char* buf, int len );
+void mxit_close_connection( struct MXitSession* session );
+
+
+#endif /* _MXIT_PROTO_H_ */
+
diff --git a/libpurple/protocols/mxit/roster.c b/libpurple/protocols/mxit/roster.c
new file mode 100644
index 0000000000..26604802ee
--- /dev/null
+++ b/libpurple/protocols/mxit/roster.c
@@ -0,0 +1,722 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- user roster management (mxit contacts) --
+ *
+ * Pieter Loubser <libpurple@mxit.com>
+ *
+ * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
+ * <http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "purple.h"
+
+#include "protocol.h"
+#include "mxit.h"
+#include "roster.h"
+
+
+struct contact_invite {
+ struct MXitSession* session; /* MXit session object */
+ struct contact* contact; /* The contact performing the invite */
+};
+
+
+/*========================================================================================================================
+ * Presence / Status
+ */
+
+/* statuses (reference: libpurple/status.h) */
+static struct status
+{
+ PurpleStatusPrimitive primative;
+ int mxit;
+ const char* id;
+ const char* name;
+} const mxit_statuses[] = {
+ /* primative, no, id, name */
+ { PURPLE_STATUS_OFFLINE, MXIT_PRESENCE_OFFLINE, "offline", "Offline" }, /* 0 */
+ { PURPLE_STATUS_AVAILABLE, MXIT_PRESENCE_ONLINE, "online", "Available" }, /* 1 */
+ { PURPLE_STATUS_AWAY, MXIT_PRESENCE_AWAY, "away", "Away" }, /* 2 */
+ { PURPLE_STATUS_AVAILABLE, MXIT_PRESENCE_AVAILABLE, "chat", "Chatty" }, /* 3 */
+ { PURPLE_STATUS_UNAVAILABLE, MXIT_PRESENCE_DND, "dnd", "Do Not Disturb" } /* 4 */
+};
+
+
+/*------------------------------------------------------------------------
+ * Return list of supported statuses. (see status.h)
+ *
+ * @param account The MXit account object
+ * @return List of PurpleStatusType
+ */
+GList* mxit_status_types( PurpleAccount* account )
+{
+ GList* statuslist = NULL;
+ PurpleStatusType* type;
+ unsigned int i;
+
+ for ( i = 0; i < ARRAY_SIZE( mxit_statuses ); i++ ) {
+ const struct status* status = &mxit_statuses[i];
+
+ /* add mxit status (reference: "libpurple/status.h") */
+ type = purple_status_type_new_with_attrs( status->primative, status->id, status->name, TRUE, TRUE, FALSE,
+ "message", _( "Message" ), purple_value_new( PURPLE_TYPE_STRING ),
+ NULL );
+
+ statuslist = g_list_append( statuslist, type );
+ }
+
+ return statuslist;
+}
+
+
+/*------------------------------------------------------------------------
+ * Returns the MXit presence code, given the unique status ID.
+ *
+ * @param id The status ID
+ * @return The MXit presence code
+ */
+int mxit_convert_presence( const char* id )
+{
+ unsigned int i;
+
+ for ( i = 0; i < ARRAY_SIZE( mxit_statuses ); i++ ) {
+ if ( strcmp( mxit_statuses[i].id, id ) == 0 ) /* status found! */
+ return mxit_statuses[i].mxit;
+ }
+
+ return -1;
+}
+
+
+/*------------------------------------------------------------------------
+ * Returns the MXit presence as a string, given the MXit presence ID.
+ *
+ * @param no The MXit presence I (see above)
+ * @return The presence as a text string
+ */
+const char* mxit_convert_presence_to_name( short no )
+{
+ unsigned int i;
+
+ for ( i = 0; i < ARRAY_SIZE( mxit_statuses ); i++ ) {
+ if ( mxit_statuses[i].mxit == no ) /* status found! */
+ return _( mxit_statuses[i].name );
+ }
+
+ return "";
+}
+
+
+/*========================================================================================================================
+ * Moods
+ */
+
+/*------------------------------------------------------------------------
+ * Returns the MXit mood as a string, given the MXit mood's ID.
+ *
+ * @param id The MXit mood ID (see roster.h)
+ * @return The mood as a text string
+ */
+const char* mxit_convert_mood_to_name( short id )
+{
+ switch ( id ) {
+ case MXIT_MOOD_ANGRY :
+ return _( "Angry" );
+ case MXIT_MOOD_EXCITED :
+ return _( "Excited" );
+ case MXIT_MOOD_GRUMPY :
+ return _( "Grumpy" );
+ case MXIT_MOOD_HAPPY :
+ return _( "Happy" );
+ case MXIT_MOOD_INLOVE :
+ return _( "In Love" );
+ case MXIT_MOOD_INVINCIBLE :
+ return _( "Invincible" );
+ case MXIT_MOOD_SAD :
+ return _( "Sad" );
+ case MXIT_MOOD_HOT :
+ return _( "Hot" );
+ case MXIT_MOOD_SICK :
+ return _( "Sick" );
+ case MXIT_MOOD_SLEEPY :
+ return _( "Sleepy" );
+ case MXIT_MOOD_NONE :
+ default :
+ return "";
+ }
+}
+
+
+/*========================================================================================================================
+ * Subscription Types
+ */
+
+/*------------------------------------------------------------------------
+ * Returns a Contact subscription type as a string.
+ *
+ * @param subtype The subscription type
+ * @return The subscription type as a text string
+ */
+const char* mxit_convert_subtype_to_name( short subtype )
+{
+ switch ( subtype ) {
+ case MXIT_SUBTYPE_BOTH :
+ return _( "Both" );
+ case MXIT_SUBTYPE_PENDING :
+ return _( "Pending" );
+ case MXIT_SUBTYPE_ASK :
+ return _( "Invited" );
+ case MXIT_SUBTYPE_REJECTED :
+ return _( "Rejected" );
+ case MXIT_SUBTYPE_DELETED :
+ return _( "Deleted" );
+ case MXIT_SUBTYPE_NONE :
+ return _( "None" );
+ default :
+ return "";
+ }
+}
+
+
+/*========================================================================================================================
+ * Calls from the MXit Protocol layer
+ */
+
+#if 0
+/*------------------------------------------------------------------------
+ * Dump a contact's info the the debug console.
+ *
+ * @param contact The contact
+ */
+static void dump_contact( struct contact* contact )
+{
+ purple_debug_info( MXIT_PLUGIN_ID, "CONTACT: name='%s', alias='%s', group='%s', type='%i', presence='%i', mood='%i'\n",
+ contact->username, contact->alias, contact->groupname, contact->type, contact->presence, contact->mood );
+}
+#endif
+
+
+#if 0
+/*------------------------------------------------------------------------
+ * Move a buddy from one group to another
+ *
+ * @param buddy the buddy to move between groups
+ * @param group the new group to move the buddy to
+ */
+static PurpleBuddy* mxit_update_buddy_group( struct MXitSession* session, PurpleBuddy* buddy, PurpleGroup* group )
+{
+ struct contact* contact = NULL;
+ PurpleGroup* current_group = purple_buddy_get_group( buddy );
+ PurpleBuddy* newbuddy = NULL;
+
+ /* make sure the groups actually differs */
+ if ( strcmp( current_group->name, group->name ) != 0 ) {
+ /* groupnames does not match, so we need to make the update */
+
+ purple_debug_info( MXIT_PLUGIN_ID, "Moving '%s' from group '%s' to '%s'\n", buddy->alias, current_group->name, group->name );
+
+ /*
+ * XXX: libPurple does not currently provide an API to change or rename the group name
+ * for a specific buddy. One option is to remove the buddy from the list and re-adding
+ * him in the new group, but by doing that makes the buddy go offline and then online
+ * again. This is really not ideal and very iretating, but how else then?
+ */
+
+ /* create new buddy */
+ newbuddy = purple_buddy_new( session->acc, buddy->name, buddy->alias );
+ newbuddy->proto_data = buddy->proto_data;
+ buddy->proto_data = NULL;
+
+ /* remove the buddy */
+ purple_blist_remove_buddy( buddy );
+
+ /* add buddy */
+ purple_blist_add_buddy( newbuddy, NULL, group, NULL );
+
+ /* now re-instate his presence again */
+ contact = newbuddy->proto_data;
+ if ( contact ) {
+
+ /* update the buddy's status (reference: "libpurple/prpl.h") */
+ if ( contact->statusMsg )
+ purple_prpl_got_user_status( session->acc, newbuddy->name, mxit_statuses[contact->presence].id, "message", contact->statusMsg, NULL );
+ else
+ purple_prpl_got_user_status( session->acc, newbuddy->name, mxit_statuses[contact->presence].id, NULL );
+
+ /* update avatar */
+ if ( contact->avatarId ) {
+ mxit_get_avatar( session, newbuddy->name, contact->avatarId );
+ g_free( contact->avatarId );
+ contact->avatarId = NULL;
+ }
+ }
+
+ return newbuddy;
+ }
+ else
+ return buddy;
+}
+#endif
+
+
+/*------------------------------------------------------------------------
+ * A contact update packet was received from the MXit server, so update the buddy's
+ * information.
+ *
+ * @param session The MXit session object
+ * @param contact The contact
+ */
+void mxit_update_contact( struct MXitSession* session, struct contact* contact )
+{
+ PurpleBuddy* buddy = NULL;
+ PurpleGroup* group = NULL;
+ const char* id = NULL;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_update_contact: user='%s' alias='%s' group='%s'\n", contact->username, contact->alias, contact->groupname );
+
+ /*
+ * libPurple requires all contacts to be in a group.
+ * So if this MXit contact isn't in a group, pretend it is.
+ */
+ if ( *contact->groupname == '\0' ) {
+ strcpy( contact->groupname, MXIT_DEFAULT_GROUP );
+ }
+
+ /* find or create a group for this contact */
+ group = purple_find_group( contact->groupname );
+ if ( !group )
+ group = purple_group_new( contact->groupname );
+
+ /* see if the buddy is not in the group already */
+ buddy = purple_find_buddy_in_group( session->acc, contact->username, group );
+ if ( !buddy ) {
+ /* buddy not found in the group */
+
+ /* lets try finding him in all groups */
+ buddy = purple_find_buddy( session->acc, contact->username );
+ if ( buddy ) {
+ /* ok, so we found him in another group. to switch him between groups we must delete him and add him again. */
+ purple_blist_remove_buddy( buddy );
+ buddy = NULL;
+ }
+
+ /* create new buddy */
+ buddy = purple_buddy_new( session->acc, contact->username, contact->alias );
+ buddy->proto_data = contact;
+
+ /* add new buddy to list */
+ purple_blist_add_buddy( buddy, NULL, group, NULL );
+ }
+ else {
+ /* buddy was found in the group */
+
+ /* now update the buddy's alias */
+ purple_blist_alias_buddy( buddy, contact->alias );
+
+ /* replace the buddy's contact struct */
+ if ( buddy->proto_data )
+ free( buddy->proto_data );
+ buddy->proto_data = contact;
+ }
+
+ /* load buddy's avatar id */
+ id = purple_buddy_icons_get_checksum_for_user( buddy );
+ if ( id )
+ contact->avatarId = g_strdup( id );
+ else
+ contact->avatarId = NULL;
+
+ /* update the buddy's status (reference: "libpurple/prpl.h") */
+ purple_prpl_got_user_status( session->acc, contact->username, mxit_statuses[contact->presence].id, NULL );
+}
+
+
+/*------------------------------------------------------------------------
+ * A presence update packet was received from the MXit server, so update the buddy's
+ * information.
+ *
+ * @param session The MXit session object
+ * @param username The contact which presence to update
+ * @param presence The new presence state for the contact
+ * @param mood The new mood for the contact
+ * @param customMood The custom mood identifier
+ * @param statusMsg This is the contact's status message
+ * @param avatarId This is the contact's avatar id
+ */
+void mxit_update_buddy_presence( struct MXitSession* session, const char* username, short presence, short mood, const char* customMood, const char* statusMsg, const char* avatarId )
+{
+ PurpleBuddy* buddy = NULL;
+ struct contact* contact = NULL;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_update_buddy_presence: user='%s' presence=%i mood=%i customMood='%s' statusMsg='%s' avatar='%s'\n",
+ username, presence, mood, customMood, statusMsg, avatarId );
+
+ if ( ( presence < MXIT_PRESENCE_OFFLINE ) || ( presence > MXIT_PRESENCE_DND ) ) {
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_update_buddy_presence: invalid presence state %i\n", presence );
+ return; /* ignore packet */
+ }
+
+ /* find the buddy information for this contact (reference: "libpurple/blist.h") */
+ buddy = purple_find_buddy( session->acc, username );
+ if ( !buddy ) {
+ purple_debug_warning( MXIT_PLUGIN_ID, "mxit_update_buddy_presence: unable to find the buddy '%s'\n", username );
+ return;
+ }
+
+ contact = buddy->proto_data;
+ if ( !contact )
+ return;
+
+ contact->presence = presence;
+ contact->mood = mood;
+
+ g_strlcpy( contact->customMood, customMood, sizeof( contact->customMood ) );
+ // TODO: Download custom mood frame.
+
+ /* update status message */
+ if ( contact->statusMsg ) {
+ g_free( contact->statusMsg );
+ contact->statusMsg = NULL;
+ }
+ if ( statusMsg[0] != '\0' )
+ contact->statusMsg = g_strdup( statusMsg );
+
+ /* update avatarId */
+ if ( ( contact->avatarId ) && ( g_ascii_strcasecmp( contact->avatarId, avatarId ) == 0 ) ) {
+ /* avatar has not changed - do nothing */
+ }
+ else if ( avatarId[0] != '\0' ) { /* avatar has changed */
+ if ( contact->avatarId )
+ g_free( contact->avatarId );
+ contact->avatarId = g_strdup( avatarId );
+
+ /* Send request to download new avatar image */
+ mxit_get_avatar( session, username, avatarId );
+ }
+ else /* clear current avatar */
+ purple_buddy_icons_set_for_user( session->acc, username, NULL, 0, NULL );
+
+ /* update the buddy's status (reference: "libpurple/prpl.h") */
+ if ( contact->statusMsg )
+ purple_prpl_got_user_status( session->acc, username, mxit_statuses[contact->presence].id, "message", contact->statusMsg, NULL );
+ else
+ purple_prpl_got_user_status( session->acc, username, mxit_statuses[contact->presence].id, NULL );
+}
+
+
+/*------------------------------------------------------------------------
+ * update the blist cached by libPurple. We need to do this to keep
+ * libPurple and MXit's rosters in sync with each other.
+ *
+ * @param session The MXit session object
+ */
+void mxit_update_blist( struct MXitSession* session )
+{
+ PurpleBuddy* buddy = NULL;
+ GSList* list = NULL;
+ unsigned int i;
+
+ /* remove all buddies we did not receive a roster update for.
+ * these contacts must have been removed from another client */
+ list = purple_find_buddies( session->acc, NULL );
+
+ for ( i = 0; i < g_slist_length( list ); i++ ) {
+ buddy = g_slist_nth_data( list, i );
+
+ if ( !buddy->proto_data ) {
+ /* this buddy should be removed, because we did not receive him in our roster update from MXit */
+ purple_debug_info( MXIT_PLUGIN_ID, "Removed 'old' buddy from the blist '%s' (%s)\n", buddy->alias, buddy->name );
+ purple_blist_remove_buddy( buddy );
+ }
+ }
+
+ /* tell the UI to update the blist */
+ purple_blist_add_account( session->acc );
+}
+
+
+/*------------------------------------------------------------------------
+ * The user authorized an invite (subscription request).
+ *
+ * @param user_data Object associated with the invite
+ */
+static void mxit_cb_buddy_auth( gpointer user_data )
+{
+ struct contact_invite* invite = (struct contact_invite*) user_data;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_buddy_auth '%s'\n", invite->contact->username );
+
+ /* send a allow subscription packet to MXit */
+ mxit_send_allow_sub( invite->session, invite->contact->username, invite->contact->alias );
+
+ /* freeup invite object */
+ if ( invite->contact->msg )
+ g_free( invite->contact->msg );
+ g_free( invite->contact );
+ g_free( invite );
+}
+
+
+/*------------------------------------------------------------------------
+ * The user rejected an invite (subscription request).
+ *
+ * @param user_data Object associated with the invite
+ */
+static void mxit_cb_buddy_deny( gpointer user_data )
+{
+ struct contact_invite* invite = (struct contact_invite*) user_data;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_buddy_deny '%s'\n", invite->contact->username );
+
+ /* send a deny subscription packet to MXit */
+ mxit_send_deny_sub( invite->session, invite->contact->username );
+
+ /* freeup invite object */
+ if ( invite->contact->msg )
+ g_free( invite->contact->msg );
+ g_free( invite->contact );
+ g_free( invite );
+}
+
+
+/*------------------------------------------------------------------------
+ * A new subscription request packet was received from the MXit server.
+ * Prompt user to accept or reject it.
+ *
+ * @param session The MXit session object
+ * @param contact The contact performing the invite
+ */
+void mxit_new_subscription( struct MXitSession* session, struct contact* contact )
+{
+ struct contact_invite* invite;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_new_subscription from '%s' (%s)\n", contact->username, contact->alias );
+
+ invite = g_new0( struct contact_invite, 1 );
+ invite->session = session;
+ invite->contact = contact;
+
+ /* (reference: "libpurple/account.h") */
+ purple_account_request_authorization( session->acc, contact->username, NULL, contact->alias, contact->msg, FALSE, mxit_cb_buddy_auth, mxit_cb_buddy_deny, invite );
+}
+
+
+/*------------------------------------------------------------------------
+ * Return TRUE if this is a MXit Chatroom contact.
+ *
+ * @param session The MXit session object
+ * @param username The username of the contact
+ */
+gboolean is_mxit_chatroom_contact( struct MXitSession* session, const char* username )
+{
+ PurpleBuddy* buddy;
+ struct contact* contact = NULL;
+
+ /* find the buddy */
+ buddy = purple_find_buddy( session->acc, username );
+ if ( !buddy ) {
+ purple_debug_warning( MXIT_PLUGIN_ID, "is_mxit_chatroom_contact: unable to find the buddy '%s'\n", username );
+ return FALSE;
+ }
+
+ contact = buddy->proto_data;
+ if ( !contact )
+ return FALSE;
+
+ return ( contact->type == MXIT_TYPE_CHATROOM );
+}
+
+
+/*========================================================================================================================
+ * Callbacks from libpurple
+ */
+
+/*------------------------------------------------------------------------
+ * The user has added a buddy to the list, so send an invite request.
+ *
+ * @param gc The connection object
+ * @param buddy The new buddy
+ * @param group The group of the new buddy
+ */
+void mxit_add_buddy( PurpleConnection* gc, PurpleBuddy* buddy, PurpleGroup* group )
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+ GSList* list = NULL;
+ PurpleBuddy* mxbuddy = NULL;
+ unsigned int i;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_add_buddy '%s' (group='%s')\n", buddy->name, group->name );
+
+ list = purple_find_buddies( session->acc, buddy->name );
+ if ( g_slist_length( list ) == 1 ) {
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_add_buddy (scenario 1) (list:%i)\n", g_slist_length( list ) );
+ /*
+ * we only send an invite to MXit when the user is not already inside our
+ * blist. this is done because purple does an add_buddy() call when
+ * you accept an invite. so in that case the user is already
+ * in our blist and ready to be chatted to.
+ */
+ mxit_send_invite( session, buddy->name, buddy->alias, group->name );
+ }
+ else {
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_add_buddy (scenario 2) (list:%i)\n", g_slist_length( list ) );
+ /*
+ * we already have the buddy in our list, so we will only update
+ * his information here and not send another invite message
+ */
+
+ /* find the correct buddy */
+ for ( i = 0; i < g_slist_length( list ); i++ ) {
+ mxbuddy = g_slist_nth_data( list, i );
+
+ if ( mxbuddy->proto_data != NULL ) {
+ /* this is our REAL MXit buddy! */
+
+ /* now update the buddy's alias */
+ purple_blist_alias_buddy( mxbuddy, buddy->alias );
+
+ /* now update the buddy's group */
+// mxbuddy = mxit_update_buddy_group( session, mxbuddy, group );
+
+ /* send the update to the MXit server */
+ mxit_send_update_contact( session, mxbuddy->name, mxbuddy->alias, group->name );
+ }
+ }
+ }
+
+ /*
+ * we remove the buddy here from the buddy list because the MXit server
+ * will send us a proper contact update packet if this succeeds. now
+ * we do not have to worry about error handling in case of adding an
+ * invalid contact. so the user will still see the contact as offline
+ * until he eventually accepts the invite.
+ */
+ purple_blist_remove_buddy( buddy );
+
+ g_slist_free( list );
+}
+
+
+/*------------------------------------------------------------------------
+ * The user has removed a buddy from the list.
+ *
+ * @param gc The connection object
+ * @param buddy The buddy being removed
+ * @param group The group the buddy was in
+ */
+void mxit_remove_buddy( PurpleConnection* gc, PurpleBuddy* buddy, PurpleGroup* group )
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_remove_buddy '%s'\n", buddy->name );
+
+ mxit_send_remove( session, buddy->name );
+}
+
+
+/*------------------------------------------------------------------------
+ * The user changed the buddy's alias.
+ *
+ * @param gc The connection object
+ * @param who The username of the buddy
+ * @param alias The new alias
+ */
+void mxit_buddy_alias( PurpleConnection* gc, const char* who, const char* alias )
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+ PurpleBuddy* buddy = NULL;
+ PurpleGroup* group = NULL;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_buddy_alias '%s' to '%s\n", who, alias );
+
+ /* find the buddy */
+ buddy = purple_find_buddy( session->acc, who );
+ if ( !buddy ) {
+ purple_debug_warning( MXIT_PLUGIN_ID, "mxit_buddy_alias: unable to find the buddy '%s'\n", who );
+ return;
+ }
+
+ /* find buddy group */
+ group = purple_buddy_get_group( buddy );
+ if ( !group ) {
+ purple_debug_warning( MXIT_PLUGIN_ID, "mxit_buddy_alias: unable to find the group for buddy '%s'\n", who );
+ return;
+ }
+
+ mxit_send_update_contact( session, who, alias, group->name );
+}
+
+
+/*------------------------------------------------------------------------
+ * The user changed the group for a single buddy.
+ *
+ * @param gc The connection object
+ * @param who The username of the buddy
+ * @param old_group The old group's name
+ * @param new_group The new group's name
+ */
+void mxit_buddy_group( PurpleConnection* gc, const char* who, const char* old_group, const char* new_group )
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+ PurpleBuddy* buddy = NULL;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_buddy_group from '%s' to '%s'\n", old_group, new_group );
+
+ /* find the buddy */
+ buddy = purple_find_buddy( session->acc, who );
+ if ( !buddy ) {
+ purple_debug_warning( MXIT_PLUGIN_ID, "mxit_buddy_group: unable to find the buddy '%s'\n", who );
+ return;
+ }
+
+ mxit_send_update_contact( session, who, buddy->alias, new_group );
+}
+
+
+/*------------------------------------------------------------------------
+ * The user has selected to rename a group, so update all contacts in that
+ * group.
+ *
+ * @param gc The connection object
+ * @param old_name The old group name
+ * @param group The updated group object
+ * @param moved_buddies The buddies affected by the rename
+ */
+void mxit_rename_group( PurpleConnection* gc, const char* old_name, PurpleGroup* group, GList* moved_buddies )
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+ PurpleBuddy* buddy = NULL;
+ GList* item = NULL;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_rename_group from '%s' to '%s\n", old_name, group->name );
+
+ // TODO: Might be more efficient to use the "rename group" command (cmd=29).
+
+ /* loop through all the contacts in the group and send updates */
+ item = moved_buddies;
+ while ( item ) {
+ buddy = item->data;
+ mxit_send_update_contact( session, buddy->name, buddy->alias, group->name );
+ item = g_list_next( item );
+ }
+}
+
diff --git a/libpurple/protocols/mxit/roster.h b/libpurple/protocols/mxit/roster.h
new file mode 100644
index 0000000000..27c022a280
--- /dev/null
+++ b/libpurple/protocols/mxit/roster.h
@@ -0,0 +1,139 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- user roster management (mxit contacts) --
+ *
+ * Pieter Loubser <libpurple@mxit.com>
+ *
+ * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
+ * <http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#ifndef _MXIT_ROSTER_H_
+#define _MXIT_ROSTER_H_
+
+
+/* MXit contact presence states */
+#define MXIT_PRESENCE_OFFLINE 0x00
+#define MXIT_PRESENCE_ONLINE 0x01
+#define MXIT_PRESENCE_AWAY 0x02
+#define MXIT_PRESENCE_AVAILABLE 0x03
+#define MXIT_PRESENCE_DND 0x04
+
+
+/* MXit contact types */
+#define MXIT_TYPE_MXIT 0x00
+#define MXIT_TYPE_JABBER 0x01
+#define MXIT_TYPE_MSN 0x02
+#define MXIT_TYPE_YAHOO 0x03
+#define MXIT_TYPE_ICQ 0x04
+#define MXIT_TYPE_AIM 0x05
+#define MXIT_TYPE_QQ 0x06
+#define MXIT_TYPE_WV 0x07
+#define MXIT_TYPE_BOT 0x08
+#define MXIT_TYPE_CHATROOM 0x09
+#define MXIT_TYPE_SMS 0x0A
+#define MXIT_TYPE_GROUP 0x0B
+#define MXIT_TYPE_GALLERY 0x0C
+#define MXIT_TYPE_INFO 0x0D
+#define MXIT_TYPE_MULTIMX 0x0E
+#define MXIT_TYPE_HYBRID 0x0F
+
+
+/* MXit contact moods */
+#define MXIT_MOOD_NONE 0x00
+#define MXIT_MOOD_ANGRY 0x01
+#define MXIT_MOOD_EXCITED 0x02
+#define MXIT_MOOD_GRUMPY 0x03
+#define MXIT_MOOD_HAPPY 0x04
+#define MXIT_MOOD_INLOVE 0x05
+#define MXIT_MOOD_INVINCIBLE 0x06
+#define MXIT_MOOD_SAD 0x07
+#define MXIT_MOOD_HOT 0x08
+#define MXIT_MOOD_SICK 0x09
+#define MXIT_MOOD_SLEEPY 0x0A
+
+
+/* MXit contact flags */
+#define MXIT_CFLAG_HIDDEN 0x02
+#define MXIT_CFLAG_GATEWAY 0x04
+#define MXIT_CFLAG_FOCUS_SEND_BLANK 0x20000
+
+
+/* Subscription types */
+#define MXIT_SUBTYPE_BOTH 'B'
+#define MXIT_SUBTYPE_PENDING 'P'
+#define MXIT_SUBTYPE_ASK 'A'
+#define MXIT_SUBTYPE_REJECTED 'R'
+#define MXIT_SUBTYPE_DELETED 'D'
+#define MXIT_SUBTYPE_NONE 'N'
+
+
+/* client protocol constants */
+#define MXIT_CP_MAX_JID_LEN 64
+#define MXIT_CP_MAX_GROUP_LEN 32
+#define MXIT_CP_MAX_ALIAS_LEN 48
+
+#define MXIT_DEFAULT_GROUP "MXit"
+
+
+/*
+ * a MXit contact
+ */
+struct contact {
+ char username[MXIT_CP_MAX_JID_LEN+1]; /* unique contact name (with domain) */
+ char alias[MXIT_CP_MAX_GROUP_LEN+1]; /* contact alias (what will be seen) */
+ char groupname[MXIT_CP_MAX_ALIAS_LEN+1]; /* contact group name */
+
+ short type; /* contact type */
+ short mood; /* contact current mood */
+ int flags; /* contact flags */
+ short presence; /* presence state */
+ short subtype; /* subscription type */
+
+ char* msg; /* invite message */
+
+ char customMood[16]; /* custom mood */
+ char* statusMsg; /* status message */
+ char* avatarId; /* avatarId */
+};
+
+/* Presence / Status */
+GList* mxit_status_types( PurpleAccount* account );
+int mxit_convert_presence( const char* id );
+const char* mxit_convert_presence_to_name( short no );
+const char* mxit_convert_subtype_to_name( short subtype );
+
+/* Moods */
+const char* mxit_convert_mood_to_name( short id );
+
+/* MXit Protocol callbacks */
+void mxit_update_contact( struct MXitSession* session, struct contact* contact );
+void mxit_update_buddy_presence( struct MXitSession* session, const char* username, short presence, short mood, const char* customMood, const char* statusMsg, const char* avatarId );
+void mxit_new_subscription( struct MXitSession* session, struct contact* contact );
+void mxit_update_blist( struct MXitSession* session );
+gboolean is_mxit_chatroom_contact( struct MXitSession* session, const char* username );
+
+/* libPurple callbacks */
+void mxit_add_buddy( PurpleConnection* gc, PurpleBuddy* buddy, PurpleGroup* group );
+void mxit_remove_buddy( PurpleConnection* gc, PurpleBuddy* buddy, PurpleGroup* group );
+void mxit_buddy_alias( PurpleConnection* gc, const char* who, const char* alias );
+void mxit_buddy_group( PurpleConnection* gc, const char* who, const char* old_group, const char* new_group );
+void mxit_rename_group( PurpleConnection* gc, const char* old_name, PurpleGroup* group, GList* moved_buddies );
+
+
+#endif /* _MXIT_ROSTER_H_ */
diff --git a/libpurple/protocols/mxit/splashscreen.c b/libpurple/protocols/mxit/splashscreen.c
new file mode 100644
index 0000000000..d4a86edfac
--- /dev/null
+++ b/libpurple/protocols/mxit/splashscreen.c
@@ -0,0 +1,223 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- splash screens --
+ *
+ * Andrew Victor <libpurple@mxit.com>
+ *
+ * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
+ * <http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include <libgen.h>
+#include <glib/gstdio.h>
+
+#include "purple.h"
+#include "imgstore.h"
+
+#include "protocol.h"
+#include "mxit.h"
+#include "splashscreen.h"
+
+
+/*------------------------------------------------------------------------
+ * Return the ID of the current splash-screen.
+ *
+ * @param session The MXit session object
+ * @return The ID of the splash-screen (or NULL if no splash-screen)
+ */
+const char* splash_current(struct MXitSession* session)
+{
+ const char* splashId = purple_account_get_string(session->acc, MXIT_CONFIG_SPLASHID, NULL);
+
+ purple_debug_info(MXIT_PLUGIN_ID, "Current splashId: '%s'\n", splashId);
+
+ if ((splashId != NULL) && (*splashId != '\0'))
+ return splashId;
+ else
+ return NULL;
+}
+
+
+/*------------------------------------------------------------------------
+ * Indicate if splash-screen popups are enabled.
+ *
+ * @param session The MXit session object
+ * @return TRUE if the popup is enabled.
+ */
+gboolean splash_popup_enabled(struct MXitSession* session)
+{
+ return purple_account_get_bool(session->acc, MXIT_CONFIG_SPLASHPOPUP, DEFAULT_SPLASH_POPUP);
+}
+
+
+/*------------------------------------------------------------------------
+ * Return if the current splash-screen is clickable.
+ *
+ * @param session The MXit session object
+ * @return TRUE or FALSE
+ */
+static gboolean splash_clickable(struct MXitSession* session)
+{
+ return purple_account_get_bool(session->acc, MXIT_CONFIG_SPLASHCLICK, FALSE);
+}
+
+
+/*------------------------------------------------------------------------
+ * Remove the stored splash-screen (if it exists).
+ *
+ * @param session The MXit session object
+ */
+void splash_remove(struct MXitSession* session)
+{
+ const char* splashId = NULL;
+ char* filename;
+
+ /* Get current splash ID */
+ splashId = splash_current(session);
+
+ if (splashId != NULL) {
+ purple_debug_info(MXIT_PLUGIN_ID, "Removing splashId: '%s'\n", splashId);
+
+ /* Delete stored splash image */
+ filename = g_strdup_printf("%s/mxit/%s.png", purple_user_dir(), splashId);
+ g_unlink(filename);
+ g_free(filename);
+
+ /* Clear current splash ID from settings */
+ purple_account_set_string(session->acc, MXIT_CONFIG_SPLASHID, "");
+ purple_account_set_bool(session->acc, MXIT_CONFIG_SPLASHCLICK, FALSE);
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Save a new splash-screen for later display.
+ *
+ * @param session The MXit session object
+ * @param splashID The ID of the splash-screen
+ * @param data Splash-screen image data (PNG format)
+ * @param datalen Splash-screen image data size
+ */
+void splash_update(struct MXitSession* session, const char* splashId, const char* data, int datalen, gboolean clickable)
+{
+ char* dir;
+ char* filename;
+
+ /* Remove the current splash-screen */
+ splash_remove(session);
+
+ /* Save the new splash image */
+ dir = g_strdup_printf("%s/mxit", purple_user_dir());
+ purple_build_dir(dir, S_IRUSR | S_IWUSR | S_IXUSR); /* ensure directory exists */
+
+ filename = g_strdup_printf("%s/%s.png", dir, splashId);
+ if (purple_util_write_data_to_file_absolute(filename, data, datalen)) {
+ /* Store new splash-screen ID to settings */
+ purple_account_set_string(session->acc, MXIT_CONFIG_SPLASHID, splashId);
+ purple_account_set_bool(session->acc, MXIT_CONFIG_SPLASHCLICK, clickable );
+ }
+
+ g_free(dir);
+ g_free(filename);
+}
+
+
+/*------------------------------------------------------------------------
+ * The user has clicked OK on the Splash request form.
+ *
+ * @param gc The connection object
+ * @param fields The list of fields in the accepted form
+ */
+static void splash_click_ok(PurpleConnection* gc, PurpleRequestFields* fields)
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+ const char* splashId;
+
+ /* Get current splash ID */
+ splashId = splash_current(session);
+ if (!splashId)
+ return;
+
+ /* if is clickable, then send click event */
+ if (splash_clickable(session))
+ mxit_send_splashclick(session, splashId);
+}
+
+
+/*------------------------------------------------------------------------
+ * Display the current splash-screen.
+ *
+ * @param session The MXit session object
+ */
+void splash_display(struct MXitSession* session)
+{
+ const char* splashId = NULL;
+ char* filename;
+ gchar* imgdata;
+ gsize imglen;
+ int imgid = -1;
+
+ /* Get current splash ID */
+ splashId = splash_current(session);
+ if (splashId == NULL) /* no splash-screen */
+ return;
+
+ purple_debug_info(MXIT_PLUGIN_ID, "Display Splash: '%s'\n", splashId);
+
+ /* Load splash-screen image from file */
+ filename = g_strdup_printf("%s/mxit/%s.png", purple_user_dir(), splashId);
+ if (g_file_get_contents(filename, &imgdata, &imglen, NULL)) {
+ char buf[128];
+
+ /* Add splash-image to imagestore */
+ imgid = purple_imgstore_add_with_id(g_memdup(imgdata, imglen), imglen, NULL);
+
+ /* Generate and display message */
+ g_snprintf(buf, sizeof(buf), "<img id=\"%d\">", imgid);
+
+ /* Open a request-type popup to display the image */
+ {
+ PurpleRequestFields* fields;
+ PurpleRequestFieldGroup* group;
+ PurpleRequestField* field;
+
+ fields = purple_request_fields_new();
+ group = purple_request_field_group_new(NULL);
+ purple_request_fields_add_group(fields, group);
+
+ field = purple_request_field_image_new("splash", "", imgdata, imglen); /* add splash image */
+ purple_request_field_group_add_field(group, field);
+
+ if (splash_clickable(session)) {
+ purple_request_fields(session->con, _("MXit Advertising"), NULL, NULL, fields,
+ _("More Information"), G_CALLBACK(splash_click_ok), _("Close"), NULL, session->acc, NULL, NULL, session->con);
+ }
+ else {
+ purple_request_fields(session->con, _("MXit Advertising"), NULL, NULL, fields,
+ _("Continue"), G_CALLBACK(splash_click_ok), _("Close"), NULL, session->acc, NULL, NULL, session->con);
+ }
+ }
+
+ /* Release reference to image */
+ purple_imgstore_unref_by_id(imgid);
+
+ g_free(imgdata);
+ }
+
+ g_free(filename);
+}
diff --git a/libpurple/protocols/mxit/splashscreen.h b/libpurple/protocols/mxit/splashscreen.h
new file mode 100644
index 0000000000..55b7fc3116
--- /dev/null
+++ b/libpurple/protocols/mxit/splashscreen.h
@@ -0,0 +1,59 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- splash screens --
+ *
+ * Andrew Victor <libpurple@mxit.com>
+ *
+ * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
+ * <http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#ifndef _MXIT_SPLASHSCREEN_H_
+#define _MXIT_SPLASHSCREEN_H_
+
+#define HANDLE_SPLASH1 "plas1.png"
+#define HANDLE_SPLASH2 "plas2.png"
+
+#define DEFAULT_SPLASH_POPUP FALSE /* disabled by default */
+
+/*
+ * Return the ID of the current splash-screen.
+ */
+const char* splash_current(struct MXitSession* session);
+
+/*
+ * Indicate if splash-screen popups are enabled.
+ */
+gboolean splash_popup_enabled();
+
+/*
+ * Save a new splash-screen.
+ */
+void splash_update(struct MXitSession* session, const char* splashId, const char* data, int datalen, gboolean clickable);
+
+/*
+ * Remove the stored splash-screen (if it exists).
+ */
+void splash_remove(struct MXitSession* session);
+
+/*
+ * Display the current splash-screen.
+ */
+void splash_display(struct MXitSession* session);
+
+#endif /* _MXIT_SPLASHSCREEN_H_ */
diff --git a/libpurple/protocols/oscar/clientlogin.c b/libpurple/protocols/oscar/clientlogin.c
index 6b131600cc..33e76082d2 100644
--- a/libpurple/protocols/oscar/clientlogin.c
+++ b/libpurple/protocols/oscar/clientlogin.c
@@ -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 @@ static gchar *generate_signature(const char *method, const char *url, const char
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 @@ static gboolean parse_start_oscar_session_response(PurpleConnection *gc, const g
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 @@ static gboolean parse_start_oscar_session_response(PurpleConnection *gc, const g
}
/* 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 @@ static gboolean parse_start_oscar_session_response(PurpleConnection *gc, const g
/* 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 @@ static gboolean parse_start_oscar_session_response(PurpleConnection *gc, const g
*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 @@ static void start_oscar_session_cb(PurpleUtilFetchUrlData *url_data, gpointer us
OscarData *od;
PurpleConnection *gc;
char *host, *cookie;
+ char *tls_certname = NULL;
unsigned short port;
guint8 *cookiedata;
gsize cookiedata_len;
@@ -244,28 +258,30 @@ static void start_oscar_session_cb(PurpleUtilFetchUrlData *url_data, gpointer us
return;
}
- if (!parse_start_oscar_session_response(gc, url_text, len, &host, &port, &cookie))
+ if (!parse_start_oscar_session_response(gc, url_text, len, &host, &port, &cookie, &tls_certname))
return;
cookiedata = purple_base64_decode(cookie, &cookiedata_len);
- oscar_connect_to_bos(gc, od, host, port, cookiedata, cookiedata_len);
+ oscar_connect_to_bos(gc, od, host, port, cookiedata, cookiedata_len, tls_certname);
g_free(cookiedata);
g_free(host);
g_free(cookie);
+ g_free(tls_certname);
}
static void send_start_oscar_session(OscarData *od, const char *token, const char *session_key, time_t hosttime)
{
char *query_string, *signature, *url;
+ gboolean use_tls = purple_account_get_bool(purple_connection_get_account(od->gc), "use_ssl", OSCAR_DEFAULT_USE_SSL);
/* Construct the GET parameters */
query_string = g_strdup_printf("a=%s"
"&f=xml"
"&k=%s"
"&ts=%" PURPLE_TIME_T_MODIFIER
- "&useTLS=0",
- purple_url_encode(token), get_client_key(od), hosttime);
+ "&useTLS=%d",
+ purple_url_encode(token), get_client_key(od), hosttime, use_tls);
signature = generate_signature("GET", URL_START_OSCAR_SESSION,
query_string, session_key);
url = g_strdup_printf(URL_START_OSCAR_SESSION "?%s&sig_sha256=%s",
diff --git a/libpurple/protocols/oscar/family_feedbag.c b/libpurple/protocols/oscar/family_feedbag.c
index 9ce98b3bb2..37e9518cd3 100644
--- a/libpurple/protocols/oscar/family_feedbag.c
+++ b/libpurple/protocols/oscar/family_feedbag.c
@@ -389,11 +389,10 @@ int aim_ssi_getpermdeny(struct aim_ssi_item *list)
/**
* 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 @@ int aim_ssi_delicon(OscarData *od)
* should show up as idle or not, etc.
*
* @param od The oscar odion.
- * @param presence I think it's a bitmask, but I only know what one of the bits is:
- * 0x00000002 - Hide wireless?
+ * @param presence A bitmask of the first 32 entries [0-31] from
+ * http://dev.aol.com/aim/oscar/#FEEDBAG__BUDDY_PREFS
+ * 0x00000002 - Hide "eBuddy group" (whatever that is)
* 0x00000400 - Allow others to see your idle time
+ * 0x00020000 - Don't show Recent Buddies
* @return Return 0 if no errors, otherwise return the error number.
*/
int aim_ssi_setpresence(OscarData *od, guint32 presence) {
diff --git a/libpurple/protocols/oscar/family_icbm.c b/libpurple/protocols/oscar/family_icbm.c
index bfe98cca5d..d3bdc70e53 100644
--- a/libpurple/protocols/oscar/family_icbm.c
+++ b/libpurple/protocols/oscar/family_icbm.c
@@ -151,6 +151,55 @@ guint16 aim_im_fingerprint(const guint8 *msghdr, int len)
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 mtn_receive(OscarData *od, FlapConnection *conn, aim_module_t *mod, F
static int
snachandler(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs)
{
- if (snac->subtype == 0x0005)
+ if (snac->subtype == 0x0001)
+ return error(od, conn, mod, frame, snac, bs);
+ else if (snac->subtype == 0x0005)
return aim_im_paraminfo(od, conn, mod, frame, snac, bs);
else if (snac->subtype == 0x0006)
return outgoingim(od, conn, mod, frame, snac, bs);
diff --git a/libpurple/protocols/oscar/family_oservice.c b/libpurple/protocols/oscar/family_oservice.c
index 1ba0f2833b..fb226a6f77 100644
--- a/libpurple/protocols/oscar/family_oservice.c
+++ b/libpurple/protocols/oscar/family_oservice.c
@@ -319,7 +319,10 @@ rateresp(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *fram
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 @@ rateresp(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *fram
* 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 @@ rateresp(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *fram
*/
/*
- * 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 @@ ratechange(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *fr
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 @@ ratechange(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *fr
rateclass->current = byte_stream_get32(bs);
rateclass->max = byte_stream_get32(bs);
- if ((userfunc = aim_callhandler(od, snac->family, snac->subtype)))
- ret = userfunc(od, conn, frame, code, classid, rateclass->windowsize, rateclass->clear, rateclass->alert, rateclass->limit, rateclass->disconnect, rateclass->current, rateclass->max);
+ if (mod->version >= 3)
+ {
+ rateclass->delta = byte_stream_get32(bs);
+ rateclass->dropping_snacs = byte_stream_get8(bs);
+
+ delta = rateclass->delta;
+
+ rateclass->last.tv_sec = now.tv_sec - delta / 1000;
+ delta %= 1000;
+ rateclass->last.tv_usec = now.tv_usec - delta * 1000;
+ }
+ else
+ {
+ rateclass->delta = rateclass->dropping_snacs = 0;
+ rateclass->last.tv_sec = now.tv_sec;
+ rateclass->last.tv_usec = now.tv_usec;
+ }
+
+ if ((userfunc = aim_callhandler(od, snac->family, snac->subtype))) {
+ /* Can't pass in guint8 via ... varargs, so we use an unsigned int */
+ unsigned int dropping_snacs = rateclass->dropping_snacs;
+ ret = userfunc(od, conn, frame, code, classid, rateclass->windowsize, rateclass->clear, rateclass->alert, rateclass->limit, rateclass->disconnect, rateclass->current, rateclass->max, rateclass->delta, dropping_snacs);
+ }
return ret;
}
diff --git a/libpurple/protocols/oscar/flap_connection.c b/libpurple/protocols/oscar/flap_connection.c
index 40125a14e0..47b068b702 100644
--- a/libpurple/protocols/oscar/flap_connection.c
+++ b/libpurple/protocols/oscar/flap_connection.c
@@ -73,7 +73,7 @@ flap_connection_send_version_with_cookie(OscarData *od, FlapConnection *conn, gu
}
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 @@ flap_connection_send_version_with_cookie_and_clientinfo(OscarData *od, FlapConne
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 @@ static guint32
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 @@ static gboolean flap_connection_send_snac_queue(FlapConnection *conn, struct tim
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 @@ flap_connection_send_snac_with_priority(OscarData *od, FlapConnection *conn, gui
gettimeofday(&now, NULL);
new_current = rateclass_get_new_current(conn, rateclass, &now);
- /* (Add 100ms padding to account for inaccuracies in the calculation) */
- if (new_current < rateclass->alert + 100)
+ if (rateclass->dropping_snacs || new_current <= rateclass->alert)
{
- purple_debug_info("oscar", "Current rate for conn %p would be %u, but we alert at %u; enqueueing\n", conn, new_current, (rateclass->alert + 100));
+ purple_debug_info("oscar", "Current rate for conn %p would be %u, but we alert at %u; enqueueing\n", conn, new_current, rateclass->alert);
enqueue = TRUE;
}
diff --git a/libpurple/protocols/oscar/libaim.c b/libpurple/protocols/oscar/libaim.c
index 5e8579a0de..1c71d7f829 100644
--- a/libpurple/protocols/oscar/libaim.c
+++ b/libpurple/protocols/oscar/libaim.c
@@ -141,7 +141,7 @@ static PurplePluginInfo info =
static void
init_plugin(PurplePlugin *plugin)
{
- oscar_init(PURPLE_PLUGIN_PROTOCOL_INFO(plugin));
+ oscar_init(plugin);
}
PURPLE_INIT_PLUGIN(aim, init_plugin, info);
diff --git a/libpurple/protocols/oscar/libicq.c b/libpurple/protocols/oscar/libicq.c
index 0de97bc145..6c56f43572 100644
--- a/libpurple/protocols/oscar/libicq.c
+++ b/libpurple/protocols/oscar/libicq.c
@@ -153,7 +153,7 @@ init_plugin(PurplePlugin *plugin)
{
PurpleAccountOption *option;
- oscar_init(PURPLE_PLUGIN_PROTOCOL_INFO(plugin));
+ oscar_init(plugin);
option = purple_account_option_string_new(_("Encoding"), "encoding", OSCAR_DEFAULT_CUSTOM_ENCODING);
prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
diff --git a/libpurple/protocols/oscar/oscar.c b/libpurple/protocols/oscar/oscar.c
index 620042f876..0326f0ef70 100644
--- a/libpurple/protocols/oscar/oscar.c
+++ b/libpurple/protocols/oscar/oscar.c
@@ -144,6 +144,26 @@ static const char * const msgerrreason[] = {
};
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 @@ connection_common_established_cb(FlapConnection *conn)
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 @@ idle_reporting_pref_cb(const char *name, PurplePrefType type,
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 @@ static int purple_memrequest(OscarData *od, FlapConnection *conn, FlapFrame *fr,
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 @@ purple_parse_auth_resp(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...)
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 @@ purple_parse_auth_resp(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...)
}
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 @@ incomingim_chan4(OscarData *od, FlapConnection *conn, aim_userinfo_t *userinfo,
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 @@ static int purple_parse_msgerr(OscarData *od, FlapConnection *conn, FlapFrame *f
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 @@ static int purple_parse_msgerr(OscarData *od, FlapConnection *conn, FlapFrame *f
#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 @@ static int purple_parse_ratechange(OscarData *od, FlapConnection *conn, FlapFram
};
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 @@ static int purple_parse_ratechange(OscarData *od, FlapConnection *conn, FlapFram
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 @@ static int purple_bosrights(OscarData *od, FlapConnection *conn, FlapFrame *fr,
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 @@ static int purple_bosrights(OscarData *od, FlapConnection *conn, FlapFrame *fr,
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 @@ static int purple_bosrights(OscarData *od, FlapConnection *conn, FlapFrame *fr,
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 @@ static int purple_ssi_parselist(OscarData *od, FlapConnection *conn, FlapFrame *
{ /* 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 @@ static int purple_ssi_parselist(OscarData *od, FlapConnection *conn, FlapFrame *
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 @@ static int purple_ssi_parselist(OscarData *od, FlapConnection *conn, FlapFrame *
/* 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 @@ static int purple_ssi_parselist(OscarData *od, FlapConnection *conn, FlapFrame *
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 @@ static int purple_ssi_parselist(OscarData *od, FlapConnection *conn, FlapFrame *
}
} 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 @@ static int purple_ssi_parselist(OscarData *od, FlapConnection *conn, FlapFrame *
}
} 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 @@ static int purple_ssi_parselist(OscarData *od, FlapConnection *conn, FlapFrame *
}
} 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 @@ static int purple_ssi_parselist(OscarData *od, FlapConnection *conn, FlapFrame *
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 @@ purple_ssi_parseaddmod(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...)
{
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 @@ purple_ssi_parseaddmod(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...)
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 @@ purple_ssi_parseaddmod(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...)
* 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);
+ b = purple_buddy_new(account, name, alias);
- if (!(g = purple_find_group(gname_utf8 ? gname_utf8 : _("Orphans")))) {
- g = purple_group_new(gname_utf8 ? gname_utf8 : _("Orphans"));
+ 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 @@ purple_ssi_parseaddmod(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...)
}
+ 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 @@ purple_ssi_parseaddmod(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...)
"group %s\n", name, gname);
}
- g_free(gname_utf8);
- g_free(alias_utf8);
-
return 1;
}
@@ -6235,7 +6297,6 @@ static void oscar_buddycb_edit_comment(PurpleBlistNode *node, gpointer ignore) {
struct name_data *data;
PurpleGroup *g;
char *comment;
- gchar *comment_utf8;
gchar *title;
PurpleAccount *account;
const char *name;
@@ -6254,7 +6315,6 @@ static void oscar_buddycb_edit_comment(PurpleBlistNode *node, gpointer ignore) {
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 @@ static void oscar_buddycb_edit_comment(PurpleBlistNode *node, gpointer ignore) {
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 @@ static void oscar_buddycb_edit_comment(PurpleBlistNode *node, gpointer ignore) {
g_free(title);
g_free(comment);
- g_free(comment_utf8);
}
static void
@@ -7076,8 +7135,9 @@ static gboolean oscar_uri_handler(const char *proto, const char *cmd, GHashTable
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 @@ void oscar_init(PurplePluginProtocolInfo *prpl_info)
OSCAR_DEFAULT_ALWAYS_USE_RV_PROXY);
prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);
- option = purple_account_option_bool_new(_("Allow multiple simultaneous logins"), "allow_multiple_logins",
- OSCAR_DEFAULT_ALLOW_MULTIPLE_LOGINS);
- prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);
+ if (g_str_equal(purple_plugin_get_id(plugin), "prpl-aim")) {
+ option = purple_account_option_bool_new(_("Allow multiple simultaneous logins"), "allow_multiple_logins",
+ OSCAR_DEFAULT_ALLOW_MULTIPLE_LOGINS);
+ prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);
+ }
if (init)
return;
diff --git a/libpurple/protocols/oscar/oscar.h b/libpurple/protocols/oscar/oscar.h
index fc6aa1da64..6ea143600b 100644
--- a/libpurple/protocols/oscar/oscar.h
+++ b/libpurple/protocols/oscar/oscar.h
@@ -535,6 +535,10 @@ struct _OscarData
struct aim_userinfo_s *userinfo;
} locate;
+ struct {
+ gboolean have_rights;
+ } bos;
+
/* Server-stored information (ssi) */
struct {
gboolean received_data;
@@ -619,7 +623,7 @@ struct aim_redirect_data
} 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_recv_cb_ssl(gpointer data, PurpleSslConnection *gsc, Purple
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 @@ int aim_bart_request(OscarData *od, const char *bn, guint8 iconcsumtype, const g
#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 @@ struct rateclass {
guint32 disconnect;
guint32 current;
guint32 max;
- guint8 unknown[5]; /* only present in versions >= 3 */
+ guint32 delta;
+ guint8 dropping_snacs;
GHashTable *members; /* Key is family and subtype, value is TRUE. */
struct timeval last; /**< The time when we last sent a SNAC of this rate class. */
diff --git a/libpurple/protocols/oscar/oscarcommon.h b/libpurple/protocols/oscar/oscarcommon.h
index df416a9385..dd4220cc0e 100644
--- a/libpurple/protocols/oscar/oscarcommon.h
+++ b/libpurple/protocols/oscar/oscarcommon.h
@@ -94,4 +94,4 @@ PurpleXfer *oscar_new_xfer(PurpleConnection *gc, const char *who);
gboolean oscar_offline_message(const PurpleBuddy *buddy);
void oscar_format_username(PurpleConnection *gc, const char *nick);
GList *oscar_actions(PurplePlugin *plugin, gpointer context);
-void oscar_init(PurplePluginProtocolInfo *prpl_info);
+void oscar_init(PurplePlugin *plugin);
diff --git a/libpurple/protocols/silc/Makefile.mingw b/libpurple/protocols/silc/Makefile.mingw
index 557b2c3511..ff4a13f584 100644
--- a/libpurple/protocols/silc/Makefile.mingw
+++ b/libpurple/protocols/silc/Makefile.mingw
@@ -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 @@ install: all $(DLL_INSTALL_DIR) $(PURPLE_INSTALL_DIR)
$(OBJECTS): $(PURPLE_CONFIG_H)
$(TARGET).dll: $(PURPLE_DLL).a $(OBJECTS)
- $(CC) -shared $(OBJECTS) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -Wl,--image-base,0x64000000 -o $(TARGET).dll
+ $(CC) -shared $(OBJECTS) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -Wl,--image-base,0x74000000 -o $(TARGET).dll
##
## CLEAN RULES
diff --git a/libpurple/protocols/silc10/Makefile.mingw b/libpurple/protocols/silc10/Makefile.mingw
index 66bcffab68..d087b331a8 100644
--- a/libpurple/protocols/silc10/Makefile.mingw
+++ b/libpurple/protocols/silc10/Makefile.mingw
@@ -7,6 +7,8 @@
PIDGIN_TREE_TOP := ../../..
include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
+DEFINES := $(subst -DWIN32_LEAN_AND_MEAN,,$(DEFINES))
+
TARGET = libsilc
NEEDED_DLLS = $(SILC_TOOLKIT)/lib/silc.dll \
$(SILC_TOOLKIT)/lib/silcclient.dll
diff --git a/libpurple/protocols/yahoo/libyahoo.c b/libpurple/protocols/yahoo/libyahoo.c
index 2f78e562d2..bd580eea4f 100644
--- a/libpurple/protocols/yahoo/libyahoo.c
+++ b/libpurple/protocols/yahoo/libyahoo.c
@@ -249,7 +249,7 @@ static PurplePluginProtocolInfo prpl_info =
yahoo_roomlist_get_list,
yahoo_roomlist_cancel,
yahoo_roomlist_expand_category,
- NULL, /* can_receive_file */
+ yahoo_can_receive_file, /* can_receive_file */
yahoo_send_file,
yahoo_new_xfer,
yahoo_offline_message, /* offline_message */
diff --git a/libpurple/protocols/yahoo/libymsg.c b/libpurple/protocols/yahoo/libymsg.c
index fcb7084ba0..385b62fc7e 100644
--- a/libpurple/protocols/yahoo/libymsg.c
+++ b/libpurple/protocols/yahoo/libymsg.c
@@ -153,7 +153,8 @@ static void yahoo_process_status(PurpleConnection *gc, struct yahoo_packet *pkt)
char *name = NULL;
gboolean unicode = FALSE;
char *message = NULL;
- char *msn_name = 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))
@@ -184,27 +185,37 @@ static void yahoo_process_status(PurpleConnection *gc, struct yahoo_packet *pkt)
f = NULL;
if (pair->value && g_utf8_validate(pair->value, -1, NULL)) {
GSList *tmplist;
- int protocol = 0;
name = pair->value;
- /* Look ahead to see if we have the protocol info about the buddy */
+ /* Look ahead to see if we have the federation info about the buddy */
for (tmplist = l->next; tmplist; tmplist = tmplist->next) {
struct yahoo_pair *p = tmplist->data;
if (p->key == 7)
break;
if (p->key == 241) {
- if(strtol(p->value, NULL, 10) == 2) {
- g_free(msn_name);
- msn_name = g_strconcat("msn/", name, NULL);
- name = msn_name;
- protocol = 2;
+ fed = strtol(p->value, NULL, 10);
+ g_free(fedname);
+ switch (fed) {
+ case YAHOO_FEDERATION_MSN:
+ name = fedname = g_strconcat("msn/", name, NULL);
+ break;
+ case YAHOO_FEDERATION_OCS:
+ name = fedname = g_strconcat("ocs/", name, NULL);
+ break;
+ case YAHOO_FEDERATION_IBM:
+ name = fedname = g_strconcat("ibm/", name, NULL);
+ break;
+ case YAHOO_FEDERATION_NONE:
+ default:
+ fedname = NULL;
+ break;
}
break;
}
}
f = yahoo_friend_find_or_new(gc, name);
- f->protocol = protocol;
+ f->fed = fed;
}
break;
case 10: /* state */
@@ -361,7 +372,7 @@ static void yahoo_process_status(PurpleConnection *gc, struct yahoo_packet *pkt)
if(f && strtol(pair->value, NULL, 10))
f->version_id = strtol(pair->value, NULL, 10);
break;
- case 241: /* protocol buddy belongs to */
+ case 241: /* Federated network buddy belongs to */
break; /* We process this when get '7' */
default:
purple_debug_warning("yahoo",
@@ -381,7 +392,8 @@ static void yahoo_process_status(PurpleConnection *gc, struct yahoo_packet *pkt)
if (name) /* update the last buddy */
yahoo_update_status(gc, name, f);
}
- g_free(msn_name);
+
+ g_free(fedname);
}
static void yahoo_do_group_check(PurpleAccount *account, GHashTable *ht, const char *name, const char *group)
@@ -488,13 +500,13 @@ static void yahoo_process_list_15(PurpleConnection *gc, struct yahoo_packet *pkt
PurpleAccount *account = purple_connection_get_account(gc);
YahooData *yd = gc->proto_data;
GHashTable *ht;
- char *norm_bud;
+ char *norm_bud = NULL;
char *temp = NULL;
YahooFriend *f = NULL; /* It's your friends. They're going to want you to share your StarBursts. */
/* But what if you had no friends? */
PurpleBuddy *b;
PurpleGroup *g;
- int protocol = 0;
+ YahooFederation fed = YAHOO_FEDERATION_NONE;
int stealth = 0;
ht = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_slist_free);
@@ -519,11 +531,20 @@ static void yahoo_process_list_15(PurpleConnection *gc, struct yahoo_packet *pkt
break;
case 301: /* This is 319 before all s/n's in a group after the first. It is followed by an identical 300. */
if(temp != NULL) {
- if(protocol == 2)
- norm_bud = g_strconcat("msn/", temp, NULL);
- else
- norm_bud = g_strdup(temp);
-
+ switch (fed) {
+ case YAHOO_FEDERATION_MSN:
+ norm_bud = g_strconcat("msn/", temp, NULL);
+ break;
+ case YAHOO_FEDERATION_OCS:
+ norm_bud = g_strconcat("ocs/", temp, NULL);
+ break;
+ case YAHOO_FEDERATION_IBM:
+ norm_bud = g_strconcat("ibm/", temp, NULL);
+ break;
+ case YAHOO_FEDERATION_NONE:
+ norm_bud = g_strdup(temp);
+ break;
+ }
if (yd->current_list15_grp) {
/* This buddy is in a group */
f = yahoo_friend_find_or_new(gc, norm_bud);
@@ -536,15 +557,15 @@ static void yahoo_process_list_15(PurpleConnection *gc, struct yahoo_packet *pkt
purple_blist_add_buddy(b, NULL, g, NULL);
}
yahoo_do_group_check(account, ht, norm_bud, yd->current_list15_grp);
- if(protocol != 0) {
- f->protocol = protocol;
- purple_debug_info("yahoo", "Setting protocol to %d\n", f->protocol);
+ if(fed) {
+ f->fed = fed;
+ purple_debug_info("yahoo", "Setting federation to %d\n", f->fed);
}
if(stealth == 2)
f->presence = YAHOO_PRESENCE_PERM_OFFLINE;
/* set p2p status not connected and no p2p packet sent */
- if(protocol == 0) {
+ if(fed == YAHOO_FEDERATION_NONE) {
yahoo_friend_set_p2p_status(f, YAHOO_P2PSTATUS_NOT_CONNECTED);
f->p2p_packet_sent = 0;
} else
@@ -556,8 +577,8 @@ static void yahoo_process_list_15(PurpleConnection *gc, struct yahoo_packet *pkt
}
g_free(norm_bud);
-
- protocol = 0;
+ norm_bud=NULL;
+ fed = YAHOO_FEDERATION_NONE;
stealth = 0;
g_free(temp);
temp = NULL;
@@ -573,8 +594,8 @@ static void yahoo_process_list_15(PurpleConnection *gc, struct yahoo_packet *pkt
g_free(temp);
temp = g_strdup(purple_normalize(account, pair->value));
break;
- case 241: /* another protocol user */
- protocol = strtol(pair->value, NULL, 10);
+ case 241: /* user on federated network */
+ fed = strtol(pair->value, NULL, 10);
break;
case 59: /* somebody told cookies come here too, but im not sure */
yahoo_process_cookie(yd, pair->value);
@@ -766,7 +787,7 @@ static void yahoo_process_notify(PurpleConnection *gc, struct yahoo_packet *pkt,
GSList *l = pkt->hash;
gint val_11 = 0;
YahooData *yd = gc->proto_data;
- gboolean msn = FALSE;
+ YahooFederation fed = YAHOO_FEDERATION_NONE;
account = purple_connection_get_account(gc);
@@ -783,8 +804,7 @@ static void yahoo_process_notify(PurpleConnection *gc, struct yahoo_packet *pkt,
if (pair->key == 11)
val_11 = strtol(pair->value, NULL, 10);
if (pair->key == 241)
- if(strtol(pair->value, NULL, 10) == 2)
- msn = TRUE;
+ fed = strtol(pair->value, NULL, 10);
l = l->next;
}
@@ -802,20 +822,30 @@ static void yahoo_process_notify(PurpleConnection *gc, struct yahoo_packet *pkt,
if (!g_ascii_strncasecmp(msg, "TYPING", strlen("TYPING"))
&& (purple_privacy_check(account, from)))
{
- if(msn) {
- char *msn_from = g_strconcat("msn/", from, NULL);
- if (*stat == '1')
- serv_got_typing(gc, msn_from, 0, PURPLE_TYPING);
- else
- serv_got_typing_stopped(gc, msn_from);
- g_free(msn_from);
- }
- else {
- if (*stat == '1')
- serv_got_typing(gc, from, 0, PURPLE_TYPING);
- else
- serv_got_typing_stopped(gc, from);
+ char *fed_from = from;
+ switch (fed) {
+ case YAHOO_FEDERATION_MSN:
+ fed_from = g_strconcat("msn/", from, NULL);
+ break;
+ case YAHOO_FEDERATION_OCS:
+ fed_from = g_strconcat("ocs/", from, NULL);
+ break;
+ case YAHOO_FEDERATION_IBM:
+ fed_from = g_strconcat("ibm/", from, NULL);
+ break;
+ case YAHOO_FEDERATION_NONE:
+ default:
+ break;
}
+
+ if (*stat == '1')
+ serv_got_typing(gc, fed_from, 0, PURPLE_TYPING);
+ else
+ serv_got_typing_stopped(gc, fed_from);
+
+ if (fed_from != from)
+ g_free(fed_from);
+
} else if (!g_ascii_strncasecmp(msg, "GAME", strlen("GAME"))) {
PurpleBuddy *bud = purple_find_buddy(account, from);
@@ -852,7 +882,8 @@ struct _yahoo_im {
int buddy_icon;
char *id;
char *msg;
- gboolean msn;
+ YahooFederation fed;
+ char *fed_from;
};
static void yahoo_process_sms_message(PurpleConnection *gc, struct yahoo_packet *pkt)
@@ -924,8 +955,6 @@ static void yahoo_process_message(PurpleConnection *gc, struct yahoo_packet *pkt
GSList *l = pkt->hash;
GSList *list = NULL;
struct _yahoo_im *im = NULL;
- const char *imv = NULL;
- gint val_11 = 0;
account = purple_connection_get_account(gc);
@@ -939,6 +968,8 @@ static void yahoo_process_message(PurpleConnection *gc, struct yahoo_packet *pkt
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;
@@ -956,18 +987,76 @@ static void yahoo_process_message(PurpleConnection *gc, struct yahoo_packet *pkt
im->msg = pair->value;
}
if (im && pair->key == 241) {
- if(strtol(pair->value, NULL, 10) == 2)
- im->msn = TRUE;
+ im->fed = strtol(pair->value, NULL, 10);
+ g_free(im->fed_from);
+ switch (im->fed) {
+ case YAHOO_FEDERATION_MSN:
+ im->fed_from = g_strconcat("msn/",im->from, NULL);
+ break;
+ case YAHOO_FEDERATION_OCS:
+ im->fed_from = g_strconcat("ocs/",im->from, NULL);
+ break;
+ case YAHOO_FEDERATION_IBM:
+ 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, 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)
@@ -979,62 +1068,19 @@ static void yahoo_process_message(PurpleConnection *gc, struct yahoo_packet *pkt
_("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 ? im->from : "(im was null)");
- /* remove from p2p connection lists, also calls yahoo_p2p_disconnect_destroy_data */
- g_hash_table_remove(yd->peers, im->from);
- return;
- }
-
- /* TODO: It seems that this check should be per IM, not global */
- /* Check for the Doodle IMV */
- 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;
- char *msn_from = NULL;
- const char *from;
- PurpleConversation *c;
im = l->data;
- if (!im->from || !im->msg) {
+ if (!im->fed_from || !im->msg) {
+ g_free(im->fed_from);
g_free(im);
continue;
}
- if (!purple_privacy_check(account, im->from)) {
- purple_debug_info("yahoo", "Message from %s dropped.\n", im->from);
+ if (!purple_privacy_check(account, im->fed_from)) {
+ purple_debug_info("yahoo", "Message from %s dropped.\n", im->fed_from);
return;
}
@@ -1069,39 +1115,26 @@ static void yahoo_process_message(PurpleConnection *gc, struct yahoo_packet *pkt
g_free(m);
m = m2;
purple_util_chrreplace(m, '\r', '\n');
-
- if (im->msn) {
- msn_from = g_strconcat("msn/", im->from, NULL);
- from = msn_from;
- } else {
- from = im->from;
- }
-
- c = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, from, account);
-
if (!strcmp(m, "<ding>")) {
char *username;
- if (c == NULL) {
- c = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, from);
- }
- username = g_markup_escape_text(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);
- g_free(msn_from);
continue;
}
m2 = yahoo_codes_to_html(m);
g_free(m);
- serv_got_im(gc, from, m2, 0, im->time);
+ serv_got_im(gc, im->fed_from, m2, 0, im->time);
g_free(m2);
- /* laters : implement buddy icon for msn friends */
- if (!im->msn) {
+ /* Official clients don't share buddy images with federated buddies */
+ if (im->fed == YAHOO_FEDERATION_NONE) {
if ((f = yahoo_friend_find(gc, im->from)) && im->buddy_icon == 2) {
if (yahoo_friend_get_buddy_icon_need_request(f)) {
yahoo_send_picture_request(gc, im->from);
@@ -1110,9 +1143,10 @@ static void yahoo_process_message(PurpleConnection *gc, struct yahoo_packet *pkt
}
}
+ g_free(im->fed_from);
g_free(im);
- g_free(msn_from);
}
+
g_slist_free(list);
}
@@ -1145,7 +1179,7 @@ struct yahoo_add_request {
PurpleConnection *gc;
char *id;
char *who;
- int protocol;
+ YahooFederation fed;
};
static void
@@ -1156,16 +1190,24 @@ yahoo_buddy_add_authorize_cb(gpointer data)
YahooData *yd = add_req->gc->proto_data;
const char *who = add_req->who;
- if (add_req->protocol == 2)
- who += 4;
-
pkt = yahoo_packet_new(YAHOO_SERVICE_AUTH_REQ_15, YAHOO_STATUS_AVAILABLE, yd->session_id);
- yahoo_packet_hash(pkt, "ssiii",
- 1, add_req->id,
- 5, who,
- 241, add_req->protocol,
- 13, 1,
- 334, 0);
+ if (add_req->fed) {
+ who += 4;
+ yahoo_packet_hash(pkt, "ssiii",
+ 1, add_req->id,
+ 5, who,
+ 241, add_req->fed,
+ 13, 1,
+ 334, 0);
+ }
+ else {
+ yahoo_packet_hash(pkt, "ssii",
+ 1, add_req->id,
+ 5, who,
+ 13, 1,
+ 334, 0);
+ }
+
yahoo_packet_send_and_free(pkt, yd);
g_free(add_req->id);
@@ -1181,23 +1223,33 @@ yahoo_buddy_add_deny_cb(struct yahoo_add_request *add_req, const char *msg)
char *encoded_msg = NULL;
const char *who = add_req->who;
- if (add_req->protocol == 2)
- who += 4; /* Skip 'msn/' */
-
if (msg && *msg)
encoded_msg = yahoo_string_encode(add_req->gc, msg, NULL);
pkt = yahoo_packet_new(YAHOO_SERVICE_AUTH_REQ_15,
YAHOO_STATUS_AVAILABLE, yd->session_id);
- yahoo_packet_hash(pkt, "ssiiiis",
- 1, add_req->id,
- 5, who,
- 241, add_req->protocol,
- 13, 2,
- 334, 0,
- 97, 1,
- 14, encoded_msg ? encoded_msg : "");
+ if (add_req->fed) {
+ who += 4; /* Skip fed identifier (msn|ocs|ibm)/' */
+ yahoo_packet_hash(pkt, "ssiiiis",
+ 1, add_req->id,
+ 5, who,
+ 241, add_req->fed,
+ 13, 2,
+ 334, 0,
+ 97, 1,
+ 14, encoded_msg ? encoded_msg : "");
+ }
+ else {
+ yahoo_packet_hash(pkt, "ssiiis",
+ 1, add_req->id,
+ 5, who,
+ 13, 2,
+ 334, 0,
+ 97, 1,
+ 14, encoded_msg ? encoded_msg : "");
+ }
+
yahoo_packet_send_and_free(pkt, yd);
@@ -1252,8 +1304,7 @@ static void yahoo_buddy_auth_req_15(PurpleConnection *gc, struct yahoo_packet *p
PurpleAccount *account;
GSList *l = pkt->hash;
const char *msg = NULL;
- int protocol = 0;
-
+
account = purple_connection_get_account(gc);
/* Buddy authorized/declined our addition */
@@ -1261,6 +1312,7 @@ static void yahoo_buddy_auth_req_15(PurpleConnection *gc, struct yahoo_packet *p
char *temp = NULL;
char *who = NULL;
int response = 0;
+ YahooFederation fed = YAHOO_FEDERATION_NONE;
while (l) {
struct yahoo_pair *pair = l->data;
@@ -1276,16 +1328,27 @@ static void yahoo_buddy_auth_req_15(PurpleConnection *gc, struct yahoo_packet *p
msg = pair->value;
break;
case 241:
- protocol = strtol(pair->value, NULL, 10);
+ fed = strtol(pair->value, NULL, 10);
break;
}
l = l->next;
}
- if(protocol == 0)
- who = g_strdup(temp);
- else if(protocol == 2)
- who = g_strconcat("msn/", temp, NULL);
+ switch (fed) {
+ case YAHOO_FEDERATION_MSN:
+ who = g_strconcat("msn/", temp, NULL);
+ break;
+ case YAHOO_FEDERATION_OCS:
+ who = g_strconcat("ocs/", temp, NULL);
+ break;
+ case YAHOO_FEDERATION_IBM:
+ who = g_strconcat("ibm/", temp, NULL);
+ break;
+ case YAHOO_FEDERATION_NONE:
+ default:
+ who = g_strdup(temp);
+ break;
+ }
if (response == 1) /* Authorized */
purple_debug_info("yahoo", "Received authorization from buddy '%s'.\n", who ? who : "(Unknown Buddy)");
@@ -1304,6 +1367,7 @@ static void yahoo_buddy_auth_req_15(PurpleConnection *gc, struct yahoo_packet *p
add_req = g_new0(struct yahoo_add_request, 1);
add_req->gc = gc;
+ add_req->fed = YAHOO_FEDERATION_NONE;
while (l) {
struct yahoo_pair *pair = l->data;
@@ -1322,7 +1386,7 @@ static void yahoo_buddy_auth_req_15(PurpleConnection *gc, struct yahoo_packet *p
firstname = pair->value;
break;
case 241:
- add_req->protocol = strtol(pair->value, NULL, 10);
+ add_req->fed = strtol(pair->value, NULL, 10);
break;
case 254:
lastname = pair->value;
@@ -1331,10 +1395,21 @@ static void yahoo_buddy_auth_req_15(PurpleConnection *gc, struct yahoo_packet *p
}
l = l->next;
}
- if(add_req->protocol == 2)
- add_req->who = g_strconcat("msn/", temp, NULL);
- else
- add_req->who = g_strdup(temp);
+ switch (add_req->fed) {
+ case YAHOO_FEDERATION_MSN:
+ add_req->who = g_strconcat("msn/", temp, NULL);
+ break;
+ case YAHOO_FEDERATION_OCS:
+ add_req->who = g_strconcat("ocs/", temp, NULL);
+ break;
+ case YAHOO_FEDERATION_IBM:
+ add_req->who = g_strconcat("ibm/", temp, NULL);
+ break;
+ case YAHOO_FEDERATION_NONE:
+ default:
+ add_req->who = g_strdup(temp);
+ break;
+ }
if (add_req->id && add_req->who) {
char *alias = NULL, *dec_msg = NULL;
@@ -2134,8 +2209,7 @@ static void yahoo_process_addbuddy(PurpleConnection *gc, struct yahoo_packet *pk
YahooFriend *f;
GSList *l = pkt->hash;
YahooData *yd = gc->proto_data;
- int protocol = 0;
- gboolean msn = FALSE;
+ YahooFederation fed = YAHOO_FEDERATION_NONE;
while (l) {
struct yahoo_pair *pair = l->data;
@@ -2151,9 +2225,7 @@ static void yahoo_process_addbuddy(PurpleConnection *gc, struct yahoo_packet *pk
group = pair->value;
break;
case 241:
- protocol = strtol(pair->value, NULL, 10);
- if(protocol == 2)
- msn = TRUE;
+ fed = strtol(pair->value, NULL, 10);
break;
}
@@ -2165,20 +2237,30 @@ static void yahoo_process_addbuddy(PurpleConnection *gc, struct yahoo_packet *pk
if (!group)
group = "";
- if(msn)
- who = g_strconcat("msn/", temp, NULL);
- else
- who = g_strdup(temp);
+ switch (fed) {
+ case YAHOO_FEDERATION_MSN:
+ who = g_strconcat("msn/", temp, NULL);
+ break;
+ case YAHOO_FEDERATION_OCS:
+ who = g_strconcat("ocs/", temp, NULL);
+ break;
+ case YAHOO_FEDERATION_IBM:
+ who = g_strconcat("ibm/", temp, NULL);
+ break;
+ case YAHOO_FEDERATION_NONE:
+ default:
+ who = g_strdup(temp);
+ break;
+ }
if (!err || (err == 2)) { /* 0 = ok, 2 = already on serv list */
f = yahoo_friend_find_or_new(gc, who);
yahoo_update_status(gc, who, f);
- if(protocol)
- f->protocol = protocol;
+ f->fed = fed;
if( !g_hash_table_lookup(yd->peers, who) ) {
/* we are not connected as client, so set friend to not connected */
- if(msn)
+ if(fed)
yahoo_friend_set_p2p_status(f, YAHOO_P2PSTATUS_DO_NOT_CONNECT);
else {
yahoo_friend_set_p2p_status(f, YAHOO_P2PSTATUS_NOT_CONNECTED);
@@ -2550,7 +2632,7 @@ void yahoo_send_p2p_pkt(PurpleConnection *gc, const char *who, int val_13)
return;
/* Dont send p2p packet to buddies of other protocols */
- if(f->protocol)
+ if(f->fed)
return;
/* Finally, don't try to connect to buddies not online or on sms */
@@ -3591,8 +3673,9 @@ const char *yahoo_list_emblem(PurpleBuddy *b)
if (purple_presence_is_online(presence)) {
if (yahoo_friend_get_game(f))
return "game";
- if (f->protocol == 2)
- return "msn";
+
+ if (f->fed)
+ return "external";
}
return NULL;
}
@@ -3916,7 +3999,7 @@ static GList *yahoo_buddy_menu(PurpleBuddy *buddy)
}
- 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),
@@ -3956,10 +4039,12 @@ static GList *yahoo_buddy_menu(PurpleBuddy *buddy)
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),
@@ -4262,11 +4347,11 @@ int yahoo_send_im(PurpleConnection *gc, const char *who, const char *what, Purpl
gboolean utf8 = TRUE;
PurpleWhiteboard *wb;
int ret = 1;
- YahooFriend *f = NULL;
+ const char *fed_who;
gsize lenb = 0;
glong lenc = 0;
struct yahoo_p2p_data *p2p_data;
- gboolean msn = FALSE;
+ YahooFederation fed = YAHOO_FEDERATION_NONE;
msg2 = yahoo_string_encode(gc, msg, &utf8);
if(msg2) {
@@ -4284,7 +4369,7 @@ int yahoo_send_im(PurpleConnection *gc, const char *who, const char *what, Purpl
}
}
- msn = !g_ascii_strncasecmp(who, "msn/", 4);
+ fed = yahoo_get_federation_from_name(who);
if (who[0] == '+') {
/* we have an sms to be sent */
@@ -4334,15 +4419,20 @@ int yahoo_send_im(PurpleConnection *gc, const char *who, const char *what, Purpl
}
pkt = yahoo_packet_new(YAHOO_SERVICE_MESSAGE, YAHOO_STATUS_OFFLINE, yd->session_id);
- if(msn) {
- yahoo_packet_hash(pkt, "ss", 1, purple_connection_get_display_name(gc), 5, who+4);
- yahoo_packet_hash_int(pkt, 241, 2);
- }
- else {
- yahoo_packet_hash(pkt, "ss", 1, purple_connection_get_display_name(gc), 5, who);
- if ((f = yahoo_friend_find(gc, who)) && f->protocol)
- yahoo_packet_hash_int(pkt, 241, f->protocol);
+ fed_who = who;
+ switch (fed) {
+ case YAHOO_FEDERATION_MSN:
+ case YAHOO_FEDERATION_OCS:
+ case YAHOO_FEDERATION_IBM:
+ fed_who += 4;
+ break;
+ case YAHOO_FEDERATION_NONE:
+ default:
+ break;
}
+ yahoo_packet_hash(pkt, "ss", 1, purple_connection_get_display_name(gc), 5, fed_who);
+ if (fed)
+ yahoo_packet_hash_int(pkt, 241, fed);
if (utf8)
yahoo_packet_hash_str(pkt, 97, "1");
@@ -4358,7 +4448,7 @@ int yahoo_send_im(PurpleConnection *gc, const char *who, const char *what, Purpl
* just so that we don't inadvertantly reset their IMVironment back
* to nothing.
*
- * If they have no set an IMVironment, then use the default.
+ * If they have not set an IMVironment, then use the default.
*/
wb = purple_whiteboard_get_session(gc->account, who);
if (wb)
@@ -4383,13 +4473,13 @@ int yahoo_send_im(PurpleConnection *gc, const char *who, const char *what, Purpl
/* We may need to not send any packets over 2000 bytes, but I'm not sure yet. */
if ((YAHOO_PACKET_HDRLEN + yahoo_packet_length(pkt)) <= 2000) {
/* if p2p link exists, send through it. To-do: key 15, time value to be sent in case of p2p */
- if( (p2p_data = g_hash_table_lookup(yd->peers, who)) && !msn ) {
+ if( (p2p_data = g_hash_table_lookup(yd->peers, who)) && !fed) {
yahoo_packet_hash_int(pkt, 11, p2p_data->session_id);
yahoo_p2p_write_pkt(p2p_data->source, pkt);
}
else {
yahoo_packet_send(pkt, yd);
- if(!msn)
+ if(!fed)
yahoo_send_p2p_pkt(gc, who, 0); /* send p2p packet, with val_13=0 */
}
}
@@ -4408,9 +4498,11 @@ unsigned int yahoo_send_typing(PurpleConnection *gc, const char *who, PurpleTypi
{
YahooData *yd = gc->proto_data;
struct yahoo_p2p_data *p2p_data;
- gboolean msn = !g_ascii_strncasecmp(who, "msn/", 4);
+ YahooFederation fed = YAHOO_FEDERATION_NONE;
struct yahoo_packet *pkt = NULL;
+ fed = yahoo_get_federation_from_name(who);
+
/* Don't do anything if sms is being typed */
if( strncmp(who, "+", 1) == 0 )
return 0;
@@ -4418,7 +4510,7 @@ unsigned int yahoo_send_typing(PurpleConnection *gc, const char *who, PurpleTypi
pkt = yahoo_packet_new(YAHOO_SERVICE_NOTIFY, YAHOO_STATUS_TYPING, yd->session_id);
/* check to see if p2p link exists, send through it */
- if( (p2p_data = g_hash_table_lookup(yd->peers, who)) && !msn ) {
+ if( (p2p_data = g_hash_table_lookup(yd->peers, who)) && !fed) {
yahoo_packet_hash(pkt, "sssssis", 49, "TYPING", 1, purple_connection_get_display_name(gc),
14, " ", 13, state == PURPLE_TYPING ? "1" : "0",
5, who, 11, p2p_data->session_id, 1002, "1"); /* To-do: key 15 to be sent in case of p2p */
@@ -4426,14 +4518,24 @@ unsigned int yahoo_send_typing(PurpleConnection *gc, const char *who, PurpleTypi
yahoo_packet_free(pkt);
}
else { /* send through yahoo server */
- if(msn)
- yahoo_packet_hash(pkt, "sssssss", 49, "TYPING", 1, purple_connection_get_display_name(gc),
- 14, " ", 13, state == PURPLE_TYPING ? "1" : "0",
- 5, who+4, 1002, "1", 241, "2");
- else
- yahoo_packet_hash(pkt, "ssssss", 49, "TYPING", 1, purple_connection_get_display_name(gc),
- 14, " ", 13, state == PURPLE_TYPING ? "1" : "0",
- 5, who, 1002, "1");
+
+ const char *fed_who = who;
+ switch (fed) {
+ case YAHOO_FEDERATION_MSN:
+ case YAHOO_FEDERATION_OCS:
+ case YAHOO_FEDERATION_IBM:
+ fed_who += 4;
+ break;
+ case YAHOO_FEDERATION_NONE:
+ default:
+ break;
+ }
+
+ yahoo_packet_hash(pkt, "ssssss", 49, "TYPING", 1, purple_connection_get_display_name(gc),
+ 14, " ", 13, state == PURPLE_TYPING ? "1" : "0",
+ 5, fed_who, 1002, "1");
+ if (fed)
+ yahoo_packet_hash_int(pkt, 241, fed);
yahoo_packet_send_and_free(pkt, yd);
}
@@ -4680,17 +4782,20 @@ void yahoo_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *g)
char *group2;
YahooFriend *f;
const char *bname;
- gboolean msn = FALSE;
+ const char *fed_bname;
+ YahooFederation fed = YAHOO_FEDERATION_NONE;
if (!yd->logged_in)
return;
- bname = purple_buddy_get_name(buddy);
+ fed_bname = bname = purple_buddy_get_name(buddy);
if (!purple_privacy_check(purple_connection_get_account(gc), bname))
return;
f = yahoo_friend_find(gc, bname);
- msn = !g_ascii_strncasecmp(bname, "msn/", 4);
+ fed = yahoo_get_federation_from_name(bname);
+ if (fed != YAHOO_FEDERATION_NONE)
+ fed_bname += 4;
g = purple_buddy_get_group(buddy);
if (g)
@@ -4700,37 +4805,35 @@ void yahoo_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *g)
group2 = yahoo_string_encode(gc, group, NULL);
pkt = yahoo_packet_new(YAHOO_SERVICE_ADDBUDDY, YAHOO_STATUS_AVAILABLE, yd->session_id);
- if(msn) {
- yahoo_packet_hash(pkt, "sssssssssss",
- 14, "",
- 65, group2,
- 97, "1",
- 1, purple_connection_get_display_name(gc),
- 302, "319",
- 300, "319",
- 7, bname + 4,
- 241, "2",
- 334, "0",
- 301, "319",
- 303, "319"
+ if (fed) {
+ yahoo_packet_hash(pkt, "sssssssisss",
+ 14, "",
+ 65, group2,
+ 97, "1",
+ 1, purple_connection_get_display_name(gc),
+ 302, "319",
+ 300, "319",
+ 7, fed_bname,
+ 241, fed,
+ 334, "0",
+ 301, "319",
+ 303, "319"
);
}
- else {
+ else {
yahoo_packet_hash(pkt, "ssssssssss",
- 14, "",
- 65, group2,
- 97, "1",
- 1, purple_connection_get_display_name(gc),
- 302, "319",
- 300, "319",
- 7, bname,
- 334, "0",
- 301, "319",
- 303, "319"
+ 14, "",
+ 65, group2,
+ 97, "1",
+ 1, purple_connection_get_display_name(gc),
+ 302, "319",
+ 300, "319",
+ 7, fed_bname,
+ 334, "0",
+ 301, "319",
+ 303, "319"
);
}
- if (f && f->protocol && !msn)
- yahoo_packet_hash_int(pkt, 241, f->protocol);
yahoo_packet_send_and_free(pkt, yd);
g_free(group2);
@@ -4746,17 +4849,16 @@ void yahoo_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *g
char *cg;
const char *bname, *gname;
YahooFriend *f = NULL;
- gboolean msn = FALSE;
+ YahooFederation fed = YAHOO_FEDERATION_NONE;
bname = purple_buddy_get_name(buddy);
f = yahoo_friend_find(gc, bname);
if (!f)
return;
+ fed = f->fed;
gname = purple_group_get_name(group);
buddies = purple_find_buddies(purple_connection_get_account(gc), bname);
- if(f->protocol == 2)
- msn = TRUE;
for (l = buddies; l; l = l->next) {
g = purple_buddy_get_group(l->data);
if (purple_utf8_strcasecmp(gname, purple_group_get_name(g))) {
@@ -4767,20 +4869,29 @@ void yahoo_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *g
g_slist_free(buddies);
- if (remove)
+ if (remove) {
g_hash_table_remove(yd->friends, bname);
+ f = NULL; /* f no longer valid - Just making it clear */
+ }
cg = yahoo_string_encode(gc, gname, NULL);
pkt = yahoo_packet_new(YAHOO_SERVICE_REMBUDDY, YAHOO_STATUS_AVAILABLE, yd->session_id);
- if(msn)
- yahoo_packet_hash(pkt, "sss", 1, purple_connection_get_display_name(gc),
- 7, bname+4, 65, cg);
- else
- yahoo_packet_hash(pkt, "sss", 1, purple_connection_get_display_name(gc),
+ switch (fed) {
+ case YAHOO_FEDERATION_MSN:
+ case YAHOO_FEDERATION_OCS:
+ case YAHOO_FEDERATION_IBM:
+ bname += 4;
+ break;
+ case YAHOO_FEDERATION_NONE:
+ default:
+ break;
+ }
+
+ yahoo_packet_hash(pkt, "sss", 1, purple_connection_get_display_name(gc),
7, bname, 65, cg);
- if(f->protocol)
- yahoo_packet_hash_int(pkt, 241, f->protocol);
+ if (fed)
+ yahoo_packet_hash_int(pkt, 241, fed);
yahoo_packet_send_and_free(pkt, yd);
g_free(cg);
}
@@ -4788,7 +4899,7 @@ void yahoo_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *g
void yahoo_add_deny(PurpleConnection *gc, const char *who) {
YahooData *yd = (YahooData *)gc->proto_data;
struct yahoo_packet *pkt;
- gboolean msn = FALSE;
+ YahooFederation fed = YAHOO_FEDERATION_NONE;
if (!yd->logged_in)
return;
@@ -4796,11 +4907,12 @@ void yahoo_add_deny(PurpleConnection *gc, const char *who) {
if (!who || who[0] == '\0')
return;
- msn = !g_ascii_strncasecmp(who, "msn/", 4);
+ fed = yahoo_get_federation_from_name(who);
+
pkt = yahoo_packet_new(YAHOO_SERVICE_IGNORECONTACT, YAHOO_STATUS_AVAILABLE, yd->session_id);
- if(msn)
- yahoo_packet_hash(pkt, "ssss", 1, purple_connection_get_display_name(gc), 7, who+4, 241, "2", 13, "1");
+ if(fed)
+ yahoo_packet_hash(pkt, "ssis", 1, purple_connection_get_display_name(gc), 7, who+4, 241, fed, 13, "1");
else
yahoo_packet_hash(pkt, "sss", 1, purple_connection_get_display_name(gc), 7, who, 13, "1");
@@ -4810,19 +4922,19 @@ void yahoo_add_deny(PurpleConnection *gc, const char *who) {
void yahoo_rem_deny(PurpleConnection *gc, const char *who) {
YahooData *yd = (YahooData *)gc->proto_data;
struct yahoo_packet *pkt;
- gboolean msn = FALSE;
+ YahooFederation fed = YAHOO_FEDERATION_NONE;
if (!yd->logged_in)
return;
if (!who || who[0] == '\0')
return;
+ fed = yahoo_get_federation_from_name(who);
- msn = !g_ascii_strncasecmp(who, "msn/", 4);
pkt = yahoo_packet_new(YAHOO_SERVICE_IGNORECONTACT, YAHOO_STATUS_AVAILABLE, yd->session_id);
- if(msn)
- yahoo_packet_hash(pkt, "ssss", 1, purple_connection_get_display_name(gc), 7, who+4, 241, "2", 13, "2");
+ if(fed)
+ yahoo_packet_hash(pkt, "ssis", 1, purple_connection_get_display_name(gc), 7, who+4, 241, fed, 13, "2");
else
yahoo_packet_hash(pkt, "sss", 1, purple_connection_get_display_name(gc), 7, who, 13, "2");
@@ -4860,7 +4972,6 @@ void yahoo_change_buddys_group(PurpleConnection *gc, const char *who,
struct yahoo_packet *pkt;
char *gpn, *gpo;
YahooFriend *f = yahoo_friend_find(gc, who);
- gboolean msn = FALSE;
const char *temp = NULL;
/* Step 0: If they aren't on the server list anyway,
@@ -4869,8 +4980,7 @@ void yahoo_change_buddys_group(PurpleConnection *gc, const char *who,
if (!f)
return;
- if(f->protocol == 2) {
- msn = TRUE;
+ if(f->fed) {
temp = who+4;
} else
temp = who;
@@ -4888,9 +4998,9 @@ void yahoo_change_buddys_group(PurpleConnection *gc, const char *who,
}
pkt = yahoo_packet_new(YAHOO_SERVICE_CHGRP_15, YAHOO_STATUS_AVAILABLE, yd->session_id);
- if(f->protocol)
+ if(f->fed)
yahoo_packet_hash(pkt, "ssssissss", 1, purple_connection_get_display_name(gc),
- 302, "240", 300, "240", 7, temp, 241, f->protocol, 224, gpo, 264, gpn, 301,
+ 302, "240", 300, "240", 7, temp, 241, f->fed, 224, gpo, 264, gpn, 301,
"240", 303, "240");
else
yahoo_packet_hash(pkt, "ssssssss", 1, purple_connection_get_display_name(gc),
diff --git a/libpurple/protocols/yahoo/libymsg.h b/libpurple/protocols/yahoo/libymsg.h
index f94dedbe32..defa5ff033 100644
--- a/libpurple/protocols/yahoo/libymsg.h
+++ b/libpurple/protocols/yahoo/libymsg.h
@@ -128,6 +128,20 @@ enum yahoo_status {
YAHOO_STATUS_DISCONNECTED = 0xffffffff /* in ymsg 15. doesnt mean the normal sense of 'disconnected' */
};
+/*
+ * Yahoo federated networks. Key 241 in ymsg.
+ * If it doesn't exist, it is on Yahoo's netowrk.
+ * It if does exist, send to another IM network.
+ */
+
+typedef enum {
+ YAHOO_FEDERATION_NONE = 0, /* No federation - Yahoo! network */
+ YAHOO_FEDERATION_OCS = 1, /* LCS or OCS private networks */
+ YAHOO_FEDERATION_MSN = 2, /* MSN or Windows Live network */
+ YAHOO_FEDERATION_IBM = 9 /* IBM/Sametime network */
+} YahooFederation;
+
+
struct yahoo_buddy_icon_upload_data {
PurpleConnection *gc;
GString *str;
@@ -332,6 +346,7 @@ char *yahoo_string_decode(PurpleConnection *gc, const char *str, gboolean utf8);
char *yahoo_convert_to_numeric(const char *str);
+YahooFederation yahoo_get_federation_from_name(const char *who);
/* yahoo_profile.c */
void yahoo_get_info(PurpleConnection *gc, const char *name);
diff --git a/libpurple/protocols/yahoo/util.c b/libpurple/protocols/yahoo/util.c
index 2299e03ea6..c4807253d0 100644
--- a/libpurple/protocols/yahoo/util.c
+++ b/libpurple/protocols/yahoo/util.c
@@ -881,6 +881,9 @@ char *yahoo_html_to_codes(const char *src)
}
g_free(etag);
}
+ } else {
+ /* We don't know what the tag is. Send it unmodified. */
+ g_string_append(dest, tag);
}
i = j;
@@ -913,3 +916,18 @@ char *yahoo_html_to_codes(const char *src)
return g_string_free(dest, FALSE);
}
+
+YahooFederation yahoo_get_federation_from_name(const char *who)
+{
+ YahooFederation fed = YAHOO_FEDERATION_NONE;
+ if (who[3] == '/') {
+ if (!g_ascii_strncasecmp(who, "msn", 3))
+ fed = YAHOO_FEDERATION_MSN;
+ else if (!g_ascii_strncasecmp(who, "ocs", 3))
+ fed = YAHOO_FEDERATION_OCS;
+ else if (!g_ascii_strncasecmp(who, "ibm", 3))
+ fed = YAHOO_FEDERATION_IBM;
+ }
+ return fed;
+}
+
diff --git a/libpurple/protocols/yahoo/yahoo_filexfer.c b/libpurple/protocols/yahoo/yahoo_filexfer.c
index c475d8f267..095b0e8fd6 100644
--- a/libpurple/protocols/yahoo/yahoo_filexfer.c
+++ b/libpurple/protocols/yahoo/yahoo_filexfer.c
@@ -1070,6 +1070,13 @@ static void yahoo_xfer_dns_connected_15(GSList *hosts, gpointer data, const char
yahoo_packet_send_and_free(pkt, yd);
}
+gboolean yahoo_can_receive_file(PurpleConnection *gc, const char *who)
+{
+ if (!who || yahoo_get_federation_from_name(who) != YAHOO_FEDERATION_NONE)
+ return FALSE;
+ return TRUE;
+}
+
void yahoo_send_file(PurpleConnection *gc, const char *who, const char *file)
{
struct yahoo_xfer_data *xfer_data;
diff --git a/libpurple/protocols/yahoo/yahoo_filexfer.h b/libpurple/protocols/yahoo/yahoo_filexfer.h
index 236cfa61bf..c31b029245 100644
--- a/libpurple/protocols/yahoo/yahoo_filexfer.h
+++ b/libpurple/protocols/yahoo/yahoo_filexfer.h
@@ -43,6 +43,18 @@ void yahoo_process_filetransfer(PurpleConnection *gc, struct yahoo_packet *pkt);
PurpleXfer *yahoo_new_xfer(PurpleConnection *gc, const char *who);
/**
+ * Returns TRUE if the buddy can receive file, FALSE otherwise.
+ * Federated users cannot receive files. So this will return FALSE only
+ * for them.
+ *
+ * @param gc The connection
+ * @param who The name of the remote user
+ *
+ * @return TRUE or FALSE
+ */
+gboolean yahoo_can_receive_file(PurpleConnection *gc, const char *who);
+
+/**
* Send a file.
*
* @param gc The PurpleConnection handle.
diff --git a/libpurple/protocols/yahoo/yahoo_friend.c b/libpurple/protocols/yahoo/yahoo_friend.c
index 342502c9dd..a3de458457 100644
--- a/libpurple/protocols/yahoo/yahoo_friend.c
+++ b/libpurple/protocols/yahoo/yahoo_friend.c
@@ -151,9 +151,8 @@ void yahoo_process_presence(PurpleConnection *gc, struct yahoo_packet *pkt)
char *temp = NULL;
char *who = NULL;
int value = 0;
- int protocol = 0;
- gboolean msn = FALSE;
-
+ YahooFederation fed = YAHOO_FEDERATION_NONE;
+
while (l) {
struct yahoo_pair *pair = l->data;
@@ -165,8 +164,7 @@ void yahoo_process_presence(PurpleConnection *gc, struct yahoo_packet *pkt)
value = strtol(pair->value, NULL, 10);
break;
case 241:
- protocol = strtol(pair->value, NULL, 10);
- msn = TRUE;
+ fed = strtol(pair->value, NULL, 10);
break;
}
@@ -177,12 +175,21 @@ void yahoo_process_presence(PurpleConnection *gc, struct yahoo_packet *pkt)
purple_debug_error("yahoo", "Received unknown value for presence key: %d\n", value);
return;
}
-
- if(msn)
- who = g_strconcat("msn/", temp, NULL);
- else
- who = g_strdup(temp);
-
+
+ switch (fed) {
+ case YAHOO_FEDERATION_MSN:
+ who = g_strconcat("msn/", temp, NULL);
+ break;
+ case YAHOO_FEDERATION_OCS:
+ who = g_strconcat("ocs/", temp, NULL);
+ break;
+ case YAHOO_FEDERATION_IBM:
+ who = g_strconcat("ibm/", temp, NULL);
+ break;
+ case YAHOO_FEDERATION_NONE:
+ who = g_strdup(temp);
+ break;
+ }
g_return_if_fail(who != NULL);
f = yahoo_friend_find(gc, who);
@@ -228,12 +235,12 @@ void yahoo_friend_update_presence(PurpleConnection *gc, const char *name,
f = yahoo_friend_find(gc, name);
if (!f)
return;
-
- if(f->protocol == 2)
+
+ if(f->fed != YAHOO_FEDERATION_NONE)
temp = name+4;
else
temp = name;
-
+
/* No need to change the value if it is already correct */
if (f->presence == presence) {
purple_debug_info("yahoo", "Not setting presence because there are no changes.\n");
@@ -258,12 +265,12 @@ void yahoo_friend_update_presence(PurpleConnection *gc, const char *name,
if (f->presence == YAHOO_PRESENCE_PERM_OFFLINE) {
pkt = yahoo_packet_new(YAHOO_SERVICE_PRESENCE_PERM,
YAHOO_STATUS_AVAILABLE, yd->session_id);
- if(f->protocol)
+ if(f->fed)
yahoo_packet_hash(pkt, "ssssssiss",
1, purple_connection_get_display_name(gc),
31, "2", 13, "2",
302, "319", 300, "319",
- 7, temp, 241, f->protocol,
+ 7, temp, 241, f->fed,
301, "319", 303, "319");
else
yahoo_packet_hash(pkt, "ssssssss",
@@ -285,12 +292,12 @@ void yahoo_friend_update_presence(PurpleConnection *gc, const char *name,
pkt = yahoo_packet_new(service,
YAHOO_STATUS_AVAILABLE, yd->session_id);
- if(f->protocol)
+ if(f->fed)
yahoo_packet_hash(pkt, "ssssssiss",
1, purple_connection_get_display_name(gc),
31, thirtyone, 13, thirteen,
302, "319", 300, "319",
- 7, temp, 241, f->protocol,
+ 7, temp, 241, f->fed,
301, "319", 303, "319");
else
yahoo_packet_hash(pkt, "ssssssss",
diff --git a/libpurple/protocols/yahoo/yahoo_friend.h b/libpurple/protocols/yahoo/yahoo_friend.h
index d2642e3a85..143d8d84d9 100644
--- a/libpurple/protocols/yahoo/yahoo_friend.h
+++ b/libpurple/protocols/yahoo/yahoo_friend.h
@@ -41,6 +41,7 @@ typedef enum {
YAHOO_P2PSTATUS_WE_ARE_CLIENT
} YahooP2PStatus;
+
/* these are called friends instead of buddies mainly so I can use variables
* named f and not confuse them with variables named b
*/
@@ -54,7 +55,7 @@ typedef struct _YahooFriend {
gchar *ip;
gboolean bicon_sent_request;
YahooPresenceVisibility presence;
- int protocol; /* 1=LCS, 2=MSN*/
+ YahooFederation fed;
long int version_id;
YahooPersonalDetails ypd;
YahooP2PStatus p2p_status;
diff --git a/libpurple/server.c b/libpurple/server.c
index 11e979c881..1bf0440355 100644
--- a/libpurple/server.c
+++ b/libpurple/server.c
@@ -786,14 +786,14 @@ void serv_got_chat_invite(PurpleConnection *gc, const char *name,
struct chat_invite_data *cid;
int plugin_return;
+ g_return_if_fail(name != NULL);
+ g_return_if_fail(who != NULL);
+
account = purple_connection_get_account(gc);
- if (PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc))->set_permit_deny == NULL) {
- /* protocol does not support privacy, handle it ourselves */
- if (!purple_privacy_check(account, who)) {
- purple_signal_emit(purple_conversations_get_handle(), "chat-invite-blocked",
- account, who, name, message, data);
- return;
- }
+ if (!purple_privacy_check(account, who)) {
+ purple_signal_emit(purple_conversations_get_handle(), "chat-invite-blocked",
+ account, who, name, message, data);
+ return;
}
cid = g_new0(struct chat_invite_data, 1);
diff --git a/libpurple/tests/test_jabber_jutil.c b/libpurple/tests/test_jabber_jutil.c
index 65c3c79e00..cc334cfceb 100644
--- a/libpurple/tests/test_jabber_jutil.c
+++ b/libpurple/tests/test_jabber_jutil.c
@@ -134,6 +134,14 @@ START_TEST(test_jabber_id_new)
assert_invalid_jid("paul@2[::1]124/as");
assert_invalid_jid("paul@まつ.おおかみ/\x01");
+ /*
+ * RFC 3454 Section 6 reads, in part,
+ * "If a string contains any RandALCat character, the
+ * string MUST NOT contain any LCat character."
+ * The character is U+066D (ARABIC FIVE POINTED STAR).
+ */
+ assert_invalid_jid("foo@example.com/٭simplexe٭");
+
/* Ensure that jabber_id_new is properly lowercasing node and domains */
assert_jid_parts("paul", "darkrain42.org", "PaUL@darkrain42.org");
assert_jid_parts("paul", "darkrain42.org", "paul@DaRkRaIn42.org");
diff --git a/libpurple/tests/test_yahoo_util.c b/libpurple/tests/test_yahoo_util.c
index 4a049c53a1..bbcc86abd3 100644
--- a/libpurple/tests/test_yahoo_util.c
+++ b/libpurple/tests/test_yahoo_util.c
@@ -180,6 +180,12 @@ START_TEST(test_html_to_codes)
assert_string_equal_free("\x1B[1mbold \x1B[#FF0000mred <font face=\"Comic Sans MS\" size=\"20\">larger \x1B[#000000mbacktoblack <font size=\"12\">normalsize</font>\x1B[#FF0000m</font>\x1B[#000000m\x1B[x1m",
yahoo_html_to_codes("<b>bold <font color=\"#FF0000\">red <font face=\"Comic Sans MS\" size=\"5\">larger <font color=\"#000000\">backtoblack <font size=\"3\">normalsize</font></font></font></font></b>"));
+
+ /* buzz/unknown tags */
+ assert_string_equal_free("<ding>",
+ yahoo_html_to_codes("<ding>"));
+ assert_string_equal_free("Unknown <tags>",
+ yahoo_html_to_codes("Unknown <tags>"));
}
END_TEST
diff --git a/libpurple/theme-loader.c b/libpurple/theme-loader.c
index 84d7bfd9da..e9b4a3ba9a 100644
--- a/libpurple/theme-loader.c
+++ b/libpurple/theme-loader.c
@@ -100,6 +100,7 @@ purple_theme_loader_finalize(GObject *obj)
PurpleThemeLoaderPrivate *priv = PURPLE_THEME_LOADER_GET_PRIVATE(loader);
g_free(priv->type);
+ g_free(priv);
parent_class->finalize(obj);
}
diff --git a/libpurple/win32/global.mak b/libpurple/win32/global.mak
index 16171dc8b9..e7f95eba63 100644
--- a/libpurple/win32/global.mak
+++ b/libpurple/win32/global.mak
@@ -85,7 +85,7 @@ ifeq ($(CYRUS_SASL), 1)
DEFINES += -DHAVE_CYRUS_SASL
endif
-DEFINES += -DHAVE_CONFIG_H
+DEFINES += -DHAVE_CONFIG_H -DWIN32_LEAN_AND_MEAN
# Use -g flag when building debug version of Pidgin (including plugins).
# Use -fnative-struct instead of -mms-bitfields when using mingw 1.1
diff --git a/pidgin.desktop.in b/pidgin.desktop.in
index fbbab51089..5dad338685 100644
--- a/pidgin.desktop.in
+++ b/pidgin.desktop.in
@@ -1,7 +1,7 @@
[Desktop Entry]
_Name=Pidgin Internet Messenger
_GenericName=Internet Messenger
-_Comment=Send instant messages over multiple protocols
+_Comment=Chat over IM. Supports AIM, Google Talk, Jabber/XMPP, MSN, Yahoo and more
Exec=pidgin
Icon=pidgin
StartupNotify=true
diff --git a/pidgin/Makefile.mingw b/pidgin/Makefile.mingw
index e1b7231581..4a77934dc4 100644
--- a/pidgin/Makefile.mingw
+++ b/pidgin/Makefile.mingw
@@ -7,6 +7,8 @@
PIDGIN_TREE_TOP := ..
include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
+DEFINES := $(subst -DWIN32_LEAN_AND_MEAN,,$(DEFINES))
+
NEEDED_DLLS = $(GTKSPELL_TOP)/gtkspell/libgtkspell.dll
##
@@ -55,12 +57,12 @@ LIB_PATHS += -L$(GTK_TOP)/lib \
##
PIDGIN_C_SRC = \
gtkaccount.c \
- gtkblist.c \
- gtkblist-theme.c \
gtkblist-theme-loader.c \
- gtkcertmgr.c \
+ gtkblist-theme.c \
+ gtkblist.c \
gtkcellrendererexpander.c \
gtkcellrendererprogress.c \
+ gtkcertmgr.c \
gtkconn.c \
gtkconv.c \
gtkdebug.c \
@@ -70,8 +72,8 @@ PIDGIN_C_SRC = \
gtkeventloop.c \
gtkexpander.c \
gtkft.c \
- gtkicon-theme.c \
gtkicon-theme-loader.c \
+ gtkicon-theme.c \
gtkidle.c \
gtkimhtml.c \
gtkimhtmltoolbar.c \
diff --git a/pidgin/gtkblist.c b/pidgin/gtkblist.c
index d7e48dda0c..4c41496658 100644
--- a/pidgin/gtkblist.c
+++ b/pidgin/gtkblist.c
@@ -4184,6 +4184,12 @@ pidgin_blist_get_name_markup(PurpleBuddy *b, gboolean selected, gboolean aliased
}
}
+ 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>*/
diff --git a/pidgin/gtkconn.c b/pidgin/gtkconn.c
index d8f9417202..6fa8e3b085 100644
--- a/pidgin/gtkconn.c
+++ b/pidgin/gtkconn.c
@@ -142,7 +142,6 @@ pidgin_connection_report_disconnect_reason (PurpleConnection *gc,
{
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 @@ pidgin_connection_report_disconnect_reason (PurpleConnection *gc,
purple_account_set_enabled(account, PIDGIN_UI, FALSE);
}
-
- /* If we have any open chats, we probably want to rejoin when we get back online. */
- list = purple_get_chats();
- while (list) {
- PurpleConversation *conv = list->data;
- list = list->next;
- if (conv->account != account ||
- purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv)))
- continue;
- purple_conversation_set_data(conv, "want-to-rejoin", GINT_TO_POINTER(TRUE));
- }
}
static void pidgin_connection_network_connected (void)
diff --git a/pidgin/gtkconv.c b/pidgin/gtkconv.c
index ee7b50739e..33b9af2860 100644
--- a/pidgin/gtkconv.c
+++ b/pidgin/gtkconv.c
@@ -4277,7 +4277,7 @@ tab_complete(PurpleConversation *conv)
/* 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);
}
@@ -4812,6 +4812,9 @@ setup_chat_userlist(PidginConversation *gtkconv, GtkWidget *hpaned)
list = gtk_tree_view_new_with_model(GTK_TREE_MODEL(ls));
+ /* Allow a user to specify gtkrc settings for the chat userlist only */
+ gtk_widget_set_name(list, "pidgin_conv_userlist");
+
rend = gtk_cell_renderer_pixbuf_new();
g_object_set(G_OBJECT(rend),
"stock-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL),
@@ -7598,6 +7601,28 @@ account_signed_off_cb(PurpleConnection *gc, gpointer event)
}
}
+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)
{
@@ -8092,6 +8117,8 @@ pidgin_conversations_init(void)
purple_signal_connect(purple_connections_get_handle(), "signed-off", handle,
G_CALLBACK(account_signed_off_cb),
GINT_TO_POINTER(PURPLE_CONV_ACCOUNT_OFFLINE));
+ purple_signal_connect(purple_connections_get_handle(), "signing-off", handle,
+ G_CALLBACK(account_signing_off), NULL);
purple_signal_connect(purple_conversations_get_handle(), "received-im-msg",
handle, G_CALLBACK(received_im_msg_cb), NULL);
diff --git a/pidgin/gtkdialogs.c b/pidgin/gtkdialogs.c
index 2bf6dfc21a..2560d0a95f 100644
--- a/pidgin/gtkdialogs.c
+++ b/pidgin/gtkdialogs.c
@@ -73,69 +73,70 @@ struct artist {
/* Order: Alphabetical by Last Name */
static const struct developer developers[] = {
- {"Daniel 'datallah' Atallah", NULL, NULL},
- {"Paul 'darkrain42' Aurich", NULL, NULL },
- {"John 'rekkanoryo' Bailey", N_("bug master"), NULL},
- {"Ethan 'Paco-Paco' Blanton", NULL, NULL},
- {"Hylke Bons", N_("artist"), "hylkebons@gmail.com"},
- {"Thomas Butter", NULL, NULL},
+ {"Daniel 'datallah' Atallah", NULL, NULL},
+ {"Paul 'darkrain42' Aurich", NULL, NULL},
+ {"John 'rekkanoryo' Bailey", N_("bug master"), NULL},
+ {"Ethan 'Paco-Paco' Blanton", NULL, NULL},
+ {"Hylke Bons", N_("artist"), "hylkebons@gmail.com"},
+ {"Thomas Butter", NULL, NULL},
/* feel free to not translate this */
- {N_("Ka-Hing Cheung"), NULL, NULL},
- {"Sadrul Habib Chowdhury", NULL, NULL},
- {"Mark 'KingAnt' Doliner", NULL, "mark@kingant.net"},
- {"Sean Egan", NULL, "sean.egan@gmail.com"},
- {"Casey Harkins", NULL, NULL},
- {"Gary 'grim' Kramlich", NULL, "grim@pidgin.im"},
- {"Richard 'rlaager' Laager", NULL, "rlaager@pidgin.im"},
- {"Richard 'wabz' Nelson", NULL, NULL},
- {"Christopher 'siege' O'Brien", NULL, "taliesein@users.sf.net"},
- {"Bartosz Oler", NULL, NULL},
- {"Etan 'deryni' Reisner", NULL, NULL},
- {"Tim 'marv' Ringenbach", NULL, NULL},
- {"Michael 'Maiku' Ruprecht", N_("voice and video"), NULL},
- {"Elliott 'QuLogic' Sales de Andrade", NULL, NULL},
- {"Luke 'LSchiere' Schierer", N_("support"), "lschiere@users.sf.net"},
- {"Evan Schoenberg", NULL, NULL},
- {"Kevin 'SimGuy' Stange", N_("webmaster"), NULL},
- {"Will 'resiak' Thompson", NULL, NULL},
- {"Stu 'nosnilmot' Tomlinson", NULL, NULL},
+ {N_("Ka-Hing Cheung"), NULL, NULL},
+ {"Sadrul Habib Chowdhury", NULL, NULL},
+ {"Mark 'KingAnt' Doliner", NULL, "mark@kingant.net"},
+ {"Sean Egan", NULL, "sean.egan@gmail.com"},
+ {"Casey Harkins", NULL, NULL},
+ {"Gary 'grim' Kramlich", NULL, "grim@pidgin.im"},
+ {"Richard 'rlaager' Laager", NULL, "rlaager@pidgin.im"},
+ {"Sulabh 'sulabh_m' Mahajan", NULL, NULL},
+ {"Richard 'wabz' Nelson", NULL, NULL},
+ {"Christopher 'siege' O'Brien", NULL, "taliesein@users.sf.net"},
+ {"Bartosz Oler", NULL, NULL},
+ {"Etan 'deryni' Reisner", NULL, NULL},
+ {"Tim 'marv' Ringenbach", NULL, NULL},
+ {"Michael 'Maiku' Ruprecht", N_("voice and video"), NULL},
+ {"Elliott 'QuLogic' Sales de Andrade", NULL, NULL},
+ {"Luke 'LSchiere' Schierer", N_("support"), "lschiere@users.sf.net"},
+ {"Evan Schoenberg", NULL, NULL},
+ {"Kevin 'SimGuy' Stange", N_("webmaster"), NULL},
+ {"Will 'resiak' Thompson", NULL, NULL},
+ {"Stu 'nosnilmot' Tomlinson", NULL, NULL},
{NULL, NULL, NULL}
};
/* Order: Alphabetical by Last Name */
static const struct developer patch_writers[] = {
- {"Marcus 'malu' Lundblad", NULL, NULL},
- {"Dennis 'EvilDennisR' Ristuccia", N_("Senior Contributor/QA"), NULL},
- {"Peter 'Fmoo' Ruibal", NULL, NULL},
- {"Gabriel 'Nix' Schulhof", NULL, NULL},
- {"Jorge 'Masca' Villaseñor", NULL, NULL},
+ {"Marcus 'malu' Lundblad", NULL, NULL},
+ {"Dennis 'EvilDennisR' Ristuccia", N_("Senior Contributor/QA"), NULL},
+ {"Peter 'Fmoo' Ruibal", NULL, NULL},
+ {"Gabriel 'Nix' Schulhof", NULL, NULL},
+ {"Jorge 'Masca' Villaseñor", NULL, NULL},
{NULL, NULL, NULL}
};
/* Order: Alphabetical by Last Name */
static const struct developer retired_developers[] = {
- {"Herman Bloggs", N_("win32 port"), "herman@bluedigits.com"},
- {"Jim Duchek", N_("maintainer"), "jim@linuxpimps.com"},
- {"Rob Flynn", N_("maintainer"), NULL},
- {"Adam Fritzler", N_("libfaim maintainer"), NULL},
- {"Christian 'ChipX86' Hammond", N_("webmaster"), NULL},
+ {"Herman Bloggs", N_("win32 port"), "herman@bluedigits.com"},
+ {"Jim Duchek", N_("maintainer"), "jim@linuxpimps.com"},
+ {"Rob Flynn", N_("maintainer"), NULL},
+ {"Adam Fritzler", N_("libfaim maintainer"), NULL},
+ {"Christian 'ChipX86' Hammond", N_("webmaster"), NULL},
/* If "lazy bum" translates literally into a serious insult, use something else or omit it. */
- {"Syd Logan", N_("hacker and designated driver [lazy bum]"), NULL},
- {"Megan 'Cae' Schneider", N_("support/QA"), NULL},
- {"Jim Seymour", N_("XMPP"), NULL},
- {"Mark Spencer", N_("original author"), "markster@marko.net"},
- {"Nathan 'faceprint' Walp", NULL, NULL},
- {"Eric Warmenhoven", N_("lead developer"), "warmenhoven@yahoo.com"},
+ {"Syd Logan", N_("hacker and designated driver [lazy bum]"), NULL},
+ {"Megan 'Cae' Schneider", N_("support/QA"), NULL},
+ {"Jim Seymour", N_("XMPP"), NULL},
+ {"Mark Spencer", N_("original author"), "markster@marko.net"},
+ {"Nathan 'faceprint' Walp", NULL, NULL},
+ {"Eric Warmenhoven", N_("lead developer"), "warmenhoven@yahoo.com"},
{NULL, NULL, NULL}
};
/* Order: Alphabetical by Last Name */
static const struct developer retired_patch_writers[] = {
- {"Felipe 'shx' Contreras", NULL, NULL},
- {"Decklin Foster", NULL, NULL},
- {"Peter 'Bleeter' Lawler", NULL, NULL},
- {"Robert 'Robot101' McQueen", NULL, NULL},
- {"Benjamin Miller", NULL, NULL},
+ {"Felipe 'shx' Contreras", NULL, NULL},
+ {"Decklin Foster", NULL, NULL},
+ {"Peter 'Bleeter' Lawler", NULL, NULL},
+ {"Robert 'Robot101' McQueen", NULL, NULL},
+ {"Benjamin Miller", NULL, NULL},
{NULL, NULL, NULL}
};
@@ -657,6 +658,12 @@ if (purple_plugins_find_with_id("core-tcl") != NULL) {
g_string_append(str, " <b>Tk:</b> Disabled<br/>");
}
+#ifdef USE_IDN
+ g_string_append(str, " <b>UTF-8 DNS (IDN):</b> Enabled<br/>");
+#else
+ g_string_append(str, " <b>UTF-8 DNS (IDN):</b> Disabled<br/>");
+#endif
+
#ifdef USE_VV
g_string_append(str, " <b>Voice and Video:</b> Enabled<br/>");
#else
diff --git a/pidgin/gtkimhtml.c b/pidgin/gtkimhtml.c
index 3ef25461b8..c7ea6595ce 100644
--- a/pidgin/gtkimhtml.c
+++ b/pidgin/gtkimhtml.c
@@ -5053,7 +5053,7 @@ void gtk_imhtml_insert_smiley_at_iter(GtkIMHtml *imhtml, const char *sml, char *
It will be destroyed when 'anchor' is destroyed. */
anchor = gtk_text_buffer_create_child_anchor(imhtml->text_buffer, iter);
g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_plaintext", text, g_free);
- g_object_set_data(G_OBJECT(anchor), "gtkimhtml_tiptext", text);
+ g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_tiptext", g_strdup(text), g_free);
g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_htmltext", g_strdup(smiley), g_free);
/* This catches the expose events generated by animated
@@ -5075,7 +5075,8 @@ void gtk_imhtml_insert_smiley_at_iter(GtkIMHtml *imhtml, const char *sml, char *
gtk_container_add(GTK_CONTAINER(ebox), img);
gtk_widget_show(img);
g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_plaintext", text, g_free);
- g_object_set_data(G_OBJECT(anchor), "gtkimhtml_tiptext", text);
+ g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_tiptext",
+ g_strdup(text), g_free);
g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_htmltext", g_strdup(smiley), g_free);
gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(imhtml), ebox, anchor);
}
diff --git a/pidgin/gtkmain.c b/pidgin/gtkmain.c
index ad73e6d1ce..4d843de7c0 100644
--- a/pidgin/gtkmain.c
+++ b/pidgin/gtkmain.c
@@ -143,6 +143,10 @@ dologin_named(const char *name)
}
#ifdef HAVE_SIGNAL_H
+static char *segfault_message;
+
+static int signal_sockets[2];
+
static void sighandler(int sig);
/*
@@ -168,31 +172,60 @@ clean_pid(void)
}
}
-char *segfault_message;
+static void sighandler(int sig)
+{
+ ssize_t written;
-/*
- * 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)
+ /*
+ * 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;
+ }
+
+ 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 @@ sighandler(int sig)
purple_debug_warning("sighandler", "Caught signal %d\n", sig);
purple_core_quit();
}
+
+ return TRUE;
}
#endif
@@ -502,10 +537,12 @@ int main(int argc, char *argv[])
sigset_t sigset;
RETSIGTYPE (*prev_sig_disp)(int);
char errmsg[BUFSIZ];
+ GIOChannel *signal_channel;
+ GIOStatus signal_status;
#ifndef DEBUG
char *segfault_message_tmp;
- GError *error = NULL;
#endif
+ GError *error = NULL;
#endif
int opt;
gboolean gui_check;
@@ -592,6 +629,29 @@ int main(int argc, char *argv[])
);
#endif
+ /*
+ * Create a socket pair for receiving unix signals from a signal
+ * handler.
+ */
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, signal_sockets) < 0) {
+ perror("Failed to create sockets for GLib signal handling");
+ exit(1);
+ }
+ signal_channel = g_io_channel_unix_new(signal_sockets[1]);
+
+ /*
+ * Set the channel encoding to raw binary instead of the default of
+ * UTF-8, because we'll be sending integers across instead of strings.
+ */
+ error = NULL;
+ signal_status = g_io_channel_set_encoding(signal_channel, NULL, &error);
+ if (signal_status != G_IO_STATUS_NORMAL) {
+ fprintf(stderr, "Failed to set the signal channel to raw "
+ "binary: %s", error->message);
+ exit(1);
+ }
+ g_io_add_watch(signal_channel, G_IO_IN, mainloop_sighandler, NULL);
+
/* Let's not violate any PLA's!!!! */
/* jseymour: whatever the fsck that means */
/* Robot101: for some reason things like gdm like to block *
@@ -744,7 +804,7 @@ int main(int argc, char *argv[])
}
#if GLIB_CHECK_VERSION(2,2,0)
- g_set_application_name(_("Pidgin"));
+ g_set_application_name(PIDGIN_NAME);
#endif /* glib-2.0 >= 2.2.0 */
#ifdef _WIN32
diff --git a/pidgin/gtkmedia.c b/pidgin/gtkmedia.c
index 2d81ada0c1..173b7ff060 100644
--- a/pidgin/gtkmedia.c
+++ b/pidgin/gtkmedia.c
@@ -89,6 +89,7 @@ struct _PidginMediaPrivate
GtkWidget *menubar;
GtkWidget *statusbar;
+ GtkWidget *hold;
GtkWidget *mute;
GtkWidget *pause;
@@ -187,6 +188,15 @@ pidgin_media_class_init (PidginMediaClass *klass)
}
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 @@ pidgin_media_ready_cb(PurpleMedia *media, PidginMedia *gtkmedia, const gchar *si
FALSE, FALSE, 0);
gtk_widget_show(GTK_WIDGET(button_widget));
gtk_widget_show(send_widget);
+
+ /* Hold button */
+ gtkmedia->priv->hold =
+ gtk_toggle_button_new_with_mnemonic("_Hold");
+ g_signal_connect(gtkmedia->priv->hold, "toggled",
+ G_CALLBACK(pidgin_media_hold_toggled),
+ gtkmedia);
+ gtk_box_pack_end(GTK_BOX(button_widget), gtkmedia->priv->hold,
+ FALSE, FALSE, 0);
+ gtk_widget_show(gtkmedia->priv->hold);
} else {
send_widget = gtkmedia->priv->send_widget;
button_widget = gtkmedia->priv->button_widget;
diff --git a/pidgin/gtkpounce.c b/pidgin/gtkpounce.c
index e2ee53335c..1261c69b8a 100644
--- a/pidgin/gtkpounce.c
+++ b/pidgin/gtkpounce.c
@@ -1455,7 +1455,7 @@ pounce_cb(PurplePounce *pounce, PurplePounceEvent events, void *data)
* Here we place the protocol name in the pounce dialog to lessen
* confusion about what protocol a pounce is for.
*/
- tmp = g_strdup_printf(
+ tmp = g_strdup(
(events & PURPLE_POUNCE_TYPING) ?
_("Started typing") :
(events & PURPLE_POUNCE_TYPED) ?
diff --git a/pidgin/gtkprefs.c b/pidgin/gtkprefs.c
index a150cb3688..fb16521841 100644
--- a/pidgin/gtkprefs.c
+++ b/pidgin/gtkprefs.c
@@ -77,6 +77,7 @@ static GtkWidget *sound_entry = NULL;
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 @@ prefs_themes_refresh(void)
_("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 @@ theme_page(void)
gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
- gtk_box_pack_start(GTK_BOX(ret), label, FALSE, TRUE, 0);
+ gtk_box_pack_start(GTK_BOX(ret), label, TRUE, TRUE, 0);
gtk_widget_show(label);
sw = gtk_scrolled_window_new(NULL,NULL);
@@ -1841,7 +1843,7 @@ network_page(void)
G_CALLBACK(network_stun_server_changed_cb), NULL);
gtk_widget_show(entry);
- pidgin_add_widget_to_vbox(GTK_BOX(vbox), "ST_UN server:",
+ pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("ST_UN server:"),
sg, entry, TRUE, NULL);
hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
@@ -1886,23 +1888,27 @@ network_page(void)
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);
+ 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(vbox, _("_Start port:"),
+ spin_button = pidgin_prefs_labeled_spin_button(hbox, _("_Start:"),
"/purple/network/ports_range_start", 0, 65535, sg);
if (!purple_prefs_get_bool("/purple/network/ports_range_use"))
gtk_widget_set_sensitive(GTK_WIDGET(spin_button), FALSE);
g_signal_connect(G_OBJECT(ports_checkbox), "clicked",
G_CALLBACK(pidgin_toggle_sensitive), spin_button);
- spin_button = pidgin_prefs_labeled_spin_button(vbox, _("_End port:"),
+ spin_button = pidgin_prefs_labeled_spin_button(hbox, _("_End:"),
"/purple/network/ports_range_end", 0, 65535, sg);
if (!purple_prefs_get_bool("/purple/network/ports_range_use"))
gtk_widget_set_sensitive(GTK_WIDGET(spin_button), FALSE);
g_signal_connect(G_OBJECT(ports_checkbox), "clicked",
G_CALLBACK(pidgin_toggle_sensitive), spin_button);
+ pidgin_add_widget_to_vbox(GTK_BOX(vbox), NULL, NULL, hbox, TRUE, NULL);
+
g_object_unref(sg);
/* TURN server */
@@ -1921,9 +1927,9 @@ network_page(void)
pidgin_prefs_labeled_spin_button(hbox, _("_Port:"),
"/purple/network/turn_port", 0, 65535, NULL);
- hbox = pidgin_prefs_labeled_entry(vbox, _("_Username:"),
+ hbox = pidgin_prefs_labeled_entry(vbox, _("Use_rname:"),
"/purple/network/turn_username", sg);
- pidgin_prefs_labeled_password(hbox, _("_Password:"),
+ pidgin_prefs_labeled_password(hbox, _("Pass_word:"),
"/purple/network/turn_password", NULL);
if (purple_running_gnome()) {
@@ -1967,9 +1973,15 @@ network_page(void)
gtk_widget_show(browser_button);
} else {
vbox = pidgin_make_frame(ret, _("Proxy Server"));
- prefs_proxy_frame = gtk_vbox_new(FALSE, 0);
+ prefs_proxy_frame = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
+ prefs_proxy_subframe = gtk_vbox_new(FALSE, 0);
- pidgin_prefs_dropdown(vbox, _("Proxy _type:"), PURPLE_PREF_STRING,
+ /* This is a global option that affects SOCKS4 usage even with account-specific proxy settings */
+ pidgin_prefs_checkbox(_("Use remote _DNS with SOCKS4 proxies"),
+ "/purple/proxy/socks4_remotedns", prefs_proxy_frame);
+ gtk_box_pack_start(GTK_BOX(vbox), prefs_proxy_frame, 0, 0, 0);
+
+ pidgin_prefs_dropdown(prefs_proxy_frame, _("Proxy t_ype:"), PURPLE_PREF_STRING,
"/purple/proxy/type",
_("No proxy"), "none",
"SOCKS 4", "socks4",
@@ -1977,21 +1989,17 @@ network_page(void)
"HTTP", "http",
_("Use Environmental Settings"), "envvar",
NULL);
- gtk_box_pack_start(GTK_BOX(vbox), prefs_proxy_frame, 0, 0, 0);
+ gtk_box_pack_start(GTK_BOX(prefs_proxy_frame), prefs_proxy_subframe, 0, 0, 0);
proxy_info = purple_global_proxy_get_info();
purple_prefs_connect_callback(prefs, "/purple/proxy/type",
- proxy_changed_cb, prefs_proxy_frame);
-
- /* This is a global option that affects SOCKS4 usage even with account-specific proxy settings */
- pidgin_prefs_checkbox(_("Use remote DNS with SOCKS4 proxies"),
- "/purple/proxy/socks4_remotedns", prefs_proxy_frame);
+ proxy_changed_cb, prefs_proxy_subframe);
table = gtk_table_new(4, 2, FALSE);
gtk_container_set_border_width(GTK_CONTAINER(table), 0);
gtk_table_set_col_spacings(GTK_TABLE(table), 5);
gtk_table_set_row_spacings(GTK_TABLE(table), 10);
- gtk_container_add(GTK_CONTAINER(prefs_proxy_frame), table);
+ gtk_container_add(GTK_CONTAINER(prefs_proxy_subframe), table);
label = gtk_label_new_with_mnemonic(_("_Host:"));
@@ -2012,11 +2020,11 @@ network_page(void)
gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
pidgin_set_accessible_label (entry, label);
- label = gtk_label_new_with_mnemonic(_("_Port:"));
+ label = gtk_label_new_with_mnemonic(_("P_ort:"));
gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
gtk_table_attach(GTK_TABLE(table), label, 2, 3, 0, 1, GTK_FILL, 0, 0, 0);
- entry = gtk_entry_new();
+ entry = gtk_spin_button_new_with_range(0, 65535, 1);
gtk_label_set_mnemonic_widget(GTK_LABEL(label), entry);
gtk_table_attach(GTK_TABLE(table), entry, 3, 4, 0, 1, GTK_FILL, 0, 0, 0);
g_signal_connect(G_OBJECT(entry), "changed",
@@ -2031,7 +2039,7 @@ network_page(void)
}
pidgin_set_accessible_label (entry, label);
- label = gtk_label_new_with_mnemonic(_("_User:"));
+ label = gtk_label_new_with_mnemonic(_("User_name:"));
gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2, GTK_FILL, 0, 0, 0);
diff --git a/pidgin/gtkrequest.c b/pidgin/gtkrequest.c
index 2c3088f034..80b23b668e 100644
--- a/pidgin/gtkrequest.c
+++ b/pidgin/gtkrequest.c
@@ -81,6 +81,33 @@ typedef struct
} 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 @@ pidgin_request_input(const char *title, const char *primary,
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 @@ pidgin_request_choice(const char *title, const char *primary,
gtk_misc_set_alignment(GTK_MISC(img), 0, 0);
gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0);
+ pidgin_widget_decorate_account(hbox, account);
+
/* Vertical box */
vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
@@ -639,6 +670,8 @@ pidgin_request_action(const char *title, const char *primary,
vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
+ pidgin_widget_decorate_account(hbox, account);
+
/* Descriptive label */
primary_esc = (primary != NULL) ? g_markup_escape_text(primary, -1) : NULL;
secondary_esc = (secondary != NULL) ? g_markup_escape_text(secondary, -1) : NULL;
@@ -1144,6 +1177,8 @@ pidgin_request_fields(const char *title, const char *primary,
GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
gtk_window_set_default(GTK_WINDOW(win), button);
+ pidgin_widget_decorate_account(hbox, account);
+
/* Setup the vbox */
vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
diff --git a/pidgin/gtkroomlist.c b/pidgin/gtkroomlist.c
index 82bac84809..f81dbfa460 100644
--- a/pidgin/gtkroomlist.c
+++ b/pidgin/gtkroomlist.c
@@ -111,7 +111,18 @@ static gint delete_win_cb(GtkWidget *w, GdkEventAny *e, gpointer d)
static void dialog_select_account_cb(GObject *w, PurpleAccount *account,
PidginRoomlistDialog *dialog)
{
+ gboolean change = (account != dialog->account);
dialog->account = account;
+
+ if (change && dialog->roomlist) {
+ PidginRoomlist *rl = dialog->roomlist->ui_data;
+ if (rl->tree) {
+ gtk_widget_destroy(rl->tree);
+ rl->tree = NULL;
+ }
+ purple_roomlist_unref(dialog->roomlist);
+ dialog->roomlist = NULL;
+ }
}
static void list_button_cb(GtkButton *button, PidginRoomlistDialog *dialog)
diff --git a/pidgin/gtkstatusbox.c b/pidgin/gtkstatusbox.c
index e0af5bb8d7..c5ad849015 100644
--- a/pidgin/gtkstatusbox.c
+++ b/pidgin/gtkstatusbox.c
@@ -79,8 +79,8 @@ static PurpleAccount* check_active_accounts_for_identical_statuses(void);
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 @@ account_status_changed_cb(PurpleAccount *account, PurpleStatus *oldstatus, Purpl
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 @@ icon_box_press_cb(GtkWidget *widget, GdkEventButton *event, PidginStatusBox *box
{
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 @@ icon_box_press_cb(GtkWidget *widget, GdkEventButton *event, PidginStatusBox *box
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 @@ pidgin_status_box_set_property(GObject *object, guint param_id,
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 @@ find_status_type_by_index(const PurpleAccount *account, gint active)
* 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 @@ status_menu_refresh_iter(PidginStatusBox *status_box)
} 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 @@ status_menu_refresh_iter(PidginStatusBox *status_box)
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 @@ add_popular_statuses(PidginStatusBox *statusbox)
* 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;
- }
+ GList *iter, *active_accts = purple_accounts_get_all_active();
+ PurpleAccount *acct1 = NULL;
+ const char *prpl1 = NULL;
- s2 = purple_account_get_status_types(acct2);
+ if (active_accts) {
+ acct1 = active_accts->data;
+ prpl1 = purple_account_get_protocol_id(acct1);
+ } else {
+ /* there's no enabled account */
+ return NULL;
+ }
- 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;
- }
+ /* start at the second account */
+ for (iter = active_accts->next; iter; iter = iter->next) {
+ PurpleAccount *acct2 = iter->data;
+ GList *s1, *s2;
- s1 = s1->next;
- s2 = s2->next;
- }
+ if (!g_str_equal(prpl1, purple_account_get_protocol_id(acct2))) {
+ acct1 = NULL;
+ break;
+ }
- if (s1 != s2) {/* Will both be NULL if matched */
- acct = NULL;
+ 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 @@ add_account_statuses(PidginStatusBox *status_box, PurpleAccount *account)
}
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_regenerate(PidginStatusBox *status_box)
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 @@ static gboolean imhtml_remove_focus(GtkWidget *w, GdkEventKey *event, PidginStat
update_to_reflect_account_status(status_box, status_box->account,
purple_account_get_active_status(status_box->account));
else {
- status_menu_refresh_iter(status_box);
+ status_menu_refresh_iter(status_box, TRUE);
pidgin_status_box_refresh(status_box);
}
return TRUE;
@@ -1229,7 +1240,7 @@ static void account_enabled_cb(PurpleAccount *acct, PidginStatusBox *status_box)
/* Regenerate the list if it has changed */
if (initial_token_acct != status_box->token_status_account) {
- pidgin_status_box_regenerate(status_box);
+ pidgin_status_box_regenerate(status_box, TRUE);
}
}
@@ -1238,13 +1249,14 @@ static void
current_savedstatus_changed_cb(PurpleSavedStatus *now, PurpleSavedStatus *old, PidginStatusBox *status_box)
{
/* Make sure our current status is added to the list of popular statuses */
- pidgin_status_box_regenerate(status_box);
+ pidgin_status_box_regenerate(status_box, TRUE);
}
static void
saved_status_updated_cb(PurpleSavedStatus *status, PidginStatusBox *status_box)
{
- pidgin_status_box_regenerate(status_box);
+ pidgin_status_box_regenerate(status_box,
+ purple_savedstatus_get_current() == status);
}
static void
@@ -1919,7 +1931,7 @@ pidgin_status_box_init (PidginStatusBox *status_box)
status_box->token_status_account = check_active_accounts_for_identical_statuses();
cache_pixbufs(status_box);
- pidgin_status_box_regenerate(status_box);
+ pidgin_status_box_regenerate(status_box, TRUE);
purple_signal_connect(purple_savedstatuses_get_handle(), "savedstatus-changed",
status_box,
@@ -2324,18 +2336,6 @@ pidgin_status_box_pulse_typing(PidginStatusBox *status_box)
pidgin_status_box_refresh(status_box);
}
-static gboolean
-message_changed(const char *one, const char *two)
-{
- if (one == NULL && two == NULL)
- return FALSE;
-
- if (one == NULL || two == NULL)
- return TRUE;
-
- return (g_utf8_collate(one, two) != 0);
-}
-
static void
activate_currently_selected_status(PidginStatusBox *status_box)
{
@@ -2386,6 +2386,7 @@ activate_currently_selected_status(PidginStatusBox *status_box)
if (status_box->account == NULL) {
PurpleStatusType *acct_status_type = NULL;
+ const char *id = NULL; /* id of acct_status_type */
PurpleStatusPrimitive primitive = GPOINTER_TO_INT(data);
/* Global */
/* Save the newly selected status to prefs.xml and status.xml */
@@ -2394,7 +2395,6 @@ activate_currently_selected_status(PidginStatusBox *status_box)
if (status_box->token_status_account) {
gint active;
PurpleStatus *status;
- const char *id = NULL;
GtkTreePath *path = gtk_tree_row_reference_get_path(status_box->active_row);
active = gtk_tree_path_get_indices(path)[0];
@@ -2402,37 +2402,35 @@ activate_currently_selected_status(PidginStatusBox *status_box)
status = purple_account_get_active_status(status_box->token_status_account);
- acct_status_type = find_status_type_by_index(status_box->token_status_account, active);
+ acct_status_type = find_status_type_by_index(status_box->token_status_account, active);
id = purple_status_type_get_id(acct_status_type);
- if (strncmp(id, purple_status_get_id(status), strlen(id)) == 0)
+ if (g_str_equal(id, purple_status_get_id(status)) &&
+ purple_strequal(message, purple_status_get_attr_string(status, "message")))
{
/* Selected status and previous status is the same */
- if (!message_changed(message, purple_status_get_attr_string(status, "message")))
- {
- PurpleSavedStatus *ss = purple_savedstatus_get_current();
- /* Make sure that statusbox displays the correct thing.
- * It can get messed up if the previous selection was a
- * saved status that wasn't supported by this account */
- if ((purple_savedstatus_get_type(ss) == primitive)
- && purple_savedstatus_is_transient(ss)
- && purple_savedstatus_has_substatuses(ss))
- changed = FALSE;
- }
+ PurpleSavedStatus *ss = purple_savedstatus_get_current();
+ /* Make sure that statusbox displays the correct thing.
+ * It can get messed up if the previous selection was a
+ * saved status that wasn't supported by this account */
+ if ((purple_savedstatus_get_type(ss) == primitive)
+ && purple_savedstatus_is_transient(ss)
+ && purple_savedstatus_has_substatuses(ss))
+ changed = FALSE;
}
} else {
saved_status = purple_savedstatus_get_current();
if (purple_savedstatus_get_type(saved_status) == primitive &&
- !purple_savedstatus_has_substatuses(saved_status))
+ !purple_savedstatus_has_substatuses(saved_status) &&
+ purple_strequal(purple_savedstatus_get_message(saved_status), message))
{
- if (!message_changed(purple_savedstatus_get_message(saved_status), message))
- changed = FALSE;
+ changed = FALSE;
}
}
if (changed)
{
- /* Manually find the appropriate transient acct */
+ /* Manually find the appropriate transient status */
if (status_box->token_status_account) {
GList *iter = purple_savedstatuses_get_all();
GList *tmp, *active_accts = purple_accounts_get_all_active();
@@ -2440,27 +2438,31 @@ activate_currently_selected_status(PidginStatusBox *status_box)
for (; iter != NULL; iter = iter->next) {
PurpleSavedStatus *ss = iter->data;
const char *ss_msg = purple_savedstatus_get_message(ss);
+ /* find a known transient status that is the same as the
+ * new selected one */
if ((purple_savedstatus_get_type(ss) == primitive) && purple_savedstatus_is_transient(ss) &&
purple_savedstatus_has_substatuses(ss) && /* Must have substatuses */
- !message_changed(ss_msg, message))
+ purple_strequal(ss_msg, message))
{
gboolean found = FALSE;
- /* The currently enabled accounts must have substatuses for all the active accts */
+ /* this status must have substatuses for all the active accts */
for(tmp = active_accts; tmp != NULL; tmp = tmp->next) {
PurpleAccount *acct = tmp->data;
PurpleSavedStatusSub *sub = purple_savedstatus_get_substatus(ss, acct);
if (sub) {
const PurpleStatusType *sub_type = purple_savedstatus_substatus_get_type(sub);
const char *subtype_status_id = purple_status_type_get_id(sub_type);
- if (subtype_status_id && !strcmp(subtype_status_id,
- purple_status_type_get_id(acct_status_type)))
+ if (purple_strequal(subtype_status_id, id)) {
found = TRUE;
+ break;
+ }
}
}
- if (!found)
- continue;
- saved_status = ss;
- break;
+
+ if (found) {
+ saved_status = ss;
+ break;
+ }
}
}
@@ -2503,11 +2505,11 @@ activate_currently_selected_status(PidginStatusBox *status_box)
status_type = find_status_type_by_index(status_box->account, active);
id = purple_status_type_get_id(status_type);
- if (strncmp(id, purple_status_get_id(status), strlen(id)) == 0)
+ if (g_str_equal(id, purple_status_get_id(status)) &&
+ purple_strequal(message, purple_status_get_attr_string(status, "message")))
{
/* Selected status and previous status is the same */
- if (!message_changed(message, purple_status_get_attr_string(status, "message")))
- changed = FALSE;
+ changed = FALSE;
}
if (changed)
@@ -2597,7 +2599,7 @@ static void remove_typing_cb(PidginStatusBox *status_box)
if (status_box->typing == 0)
{
/* Nothing has changed, so we don't need to do anything */
- status_menu_refresh_iter(status_box);
+ status_menu_refresh_iter(status_box, FALSE);
return;
}
@@ -2655,14 +2657,14 @@ static void pidgin_status_box_changed(PidginStatusBox *status_box)
pidgin_status_editor_show(FALSE,
purple_savedstatus_is_transient(saved_status)
? saved_status : NULL);
- status_menu_refresh_iter(status_box);
+ status_menu_refresh_iter(status_box, FALSE);
return;
}
if (type == PIDGIN_STATUS_BOX_TYPE_SAVED)
{
pidgin_status_window_show();
- status_menu_refresh_iter(status_box);
+ status_menu_refresh_iter(status_box, FALSE);
return;
}
}
diff --git a/pidgin/gtkutils.c b/pidgin/gtkutils.c
index 614863951f..ec5645a6a3 100644
--- a/pidgin/gtkutils.c
+++ b/pidgin/gtkutils.c
@@ -75,7 +75,7 @@ typedef struct {
} AopMenu;
static guint accels_save_timer = 0;
-static GList *gnome_url_handlers = NULL;
+static GSList *registered_url_handlers = NULL;
static gboolean
url_clicked_idle_cb(gpointer data)
@@ -3890,7 +3890,7 @@ register_gnome_url_handlers(void)
start += sizeof("/desktop/gnome/url-handlers/") - 1;
protocol = g_strdup_printf("%s:", start);
- gnome_url_handlers = g_list_prepend(gnome_url_handlers, protocol);
+ registered_url_handlers = g_slist_prepend(registered_url_handlers, protocol);
gtk_imhtml_class_register_protocol(protocol, url_clicked_cb, link_context_menu);
}
start = c + 1;
@@ -3898,9 +3898,45 @@ register_gnome_url_handlers(void)
}
g_free(tmp);
- return (gnome_url_handlers != NULL);
+ return (registered_url_handlers != NULL);
}
+#ifdef _WIN32
+static void
+winpidgin_register_win32_url_handlers(void)
+{
+ int idx = 0;
+ LONG ret = ERROR_SUCCESS;
+
+ do {
+ DWORD nameSize = 256;
+ char start[256];
+ /* I don't think we need to worry about non-ASCII protocol names */
+ ret = RegEnumKeyExA(HKEY_CLASSES_ROOT, idx++, start, &nameSize,
+ NULL, NULL, NULL, NULL);
+ if (ret == ERROR_SUCCESS) {
+ HKEY reg_key = NULL;
+ ret = RegOpenKeyExA(HKEY_CLASSES_ROOT, start, 0, KEY_READ, &reg_key);
+ if (ret == ERROR_SUCCESS) {
+ ret = RegQueryValueExA(reg_key, "URL Protocol", NULL, NULL, NULL, NULL);
+ if (ret == ERROR_SUCCESS) {
+ gchar *protocol = g_strdup_printf("%s:", start);
+ registered_url_handlers = g_slist_prepend(registered_url_handlers, protocol);
+ /* We still pass everything to the "http" "open" handler for security reasons */
+ gtk_imhtml_class_register_protocol(protocol, url_clicked_cb, link_context_menu);
+ }
+ RegCloseKey(reg_key);
+ }
+ ret = ERROR_SUCCESS;
+ }
+ } while (ret == ERROR_SUCCESS);
+
+ if (ret != ERROR_NO_MORE_ITEMS)
+ purple_debug_error("winpidgin", "Error iterating HKEY_CLASSES_ROOT subkeys: %ld\n",
+ ret);
+}
+#endif
+
void pidgin_utils_init(void)
{
gtk_imhtml_class_register_protocol("http://", url_clicked_cb, link_context_menu);
@@ -3918,6 +3954,11 @@ void pidgin_utils_init(void)
/* If we're under GNOME, try registering the system URL handlers. */
if (purple_running_gnome())
register_gnome_url_handlers();
+
+#ifdef _WIN32
+ winpidgin_register_win32_url_handlers();
+#endif
+
}
void pidgin_utils_uninit(void)
@@ -3925,16 +3966,16 @@ void pidgin_utils_uninit(void)
gtk_imhtml_class_register_protocol("open://", NULL, NULL);
/* If we have GNOME handlers registered, unregister them. */
- if (gnome_url_handlers)
+ if (registered_url_handlers)
{
- GList *l;
- for (l = gnome_url_handlers ; l ; l = l->next)
+ GSList *l;
+ for (l = registered_url_handlers; l; l = l->next)
{
gtk_imhtml_class_register_protocol((char *)l->data, NULL, NULL);
g_free(l->data);
}
- g_list_free(gnome_url_handlers);
- gnome_url_handlers = NULL;
+ g_slist_free(registered_url_handlers);
+ registered_url_handlers = NULL;
return;
}
diff --git a/pidgin/pixmaps/Makefile.am b/pidgin/pixmaps/Makefile.am
index 4e84aaafff..5583bfc05c 100644
--- a/pidgin/pixmaps/Makefile.am
+++ b/pidgin/pixmaps/Makefile.am
@@ -236,6 +236,7 @@ PROTOCOLS_16 = \
protocols/16/jabber.png \
protocols/16/meanwhile.png \
protocols/16/msn.png \
+ protocols/16/mxit.png \
protocols/16/myspace.png \
protocols/16/qq.png \
protocols/16/silc.png \
@@ -308,6 +309,7 @@ PROTOCOLS_48 = \
protocols/48/jabber.png \
protocols/48/meanwhile.png \
protocols/48/msn.png \
+ protocols/48/mxit.png \
protocols/48/myspace.png \
protocols/48/qq.png \
protocols/48/silc.png \
@@ -326,6 +328,7 @@ PROTOCOLS_SCALABLE = \
protocols/scalable/jabber.svg \
protocols/scalable/meanwhile.svg \
protocols/scalable/msn.svg \
+ protocols/scalable/mxit.svg \
protocols/scalable/qq.svg \
protocols/scalable/silc.svg \
protocols/scalable/simple.svg \
diff --git a/pidgin/pixmaps/emotes/default/24/default.theme.in b/pidgin/pixmaps/emotes/default/24/default.theme.in
index 5dab7d2c6e..8ddb77e61d 100644
--- a/pidgin/pixmaps/emotes/default/24/default.theme.in
+++ b/pidgin/pixmaps/emotes/default/24/default.theme.in
@@ -491,7 +491,7 @@ smile.png :) :-) =)
smile-big.png :D :-D =D
wink.png ;) ;-) ;^)
shock.png :-o
-tongue.png :P :-P :-p
+tongue.png :P :-P :-p :p
glasses-cool.png B-)
angry.png X-(
sad.png :( :-( =(
diff --git a/pidgin/pixmaps/emotes/small/16/small.theme.in b/pidgin/pixmaps/emotes/small/16/small.theme.in
index f9dd3713b8..64dc0e4f88 100644
--- a/pidgin/pixmaps/emotes/small/16/small.theme.in
+++ b/pidgin/pixmaps/emotes/small/16/small.theme.in
@@ -188,7 +188,7 @@ smile.png :) :-) =)
smile-big.png :D :-D =D
wink.png ;) ;-) ;^)
shock.png :-o
-tongue.png :P :-P :-p
+tongue.png :P :-P :-p :p
glasses-cool.png B-)
angry.png X-(
sad.png :( :-( =(
diff --git a/pidgin/pixmaps/protocols/16/mxit.png b/pidgin/pixmaps/protocols/16/mxit.png
new file mode 100644
index 0000000000..7ae49a17f6
--- /dev/null
+++ b/pidgin/pixmaps/protocols/16/mxit.png
Binary files differ
diff --git a/pidgin/pixmaps/protocols/22/mxit.png b/pidgin/pixmaps/protocols/22/mxit.png
new file mode 100644
index 0000000000..0e2bf38b05
--- /dev/null
+++ b/pidgin/pixmaps/protocols/22/mxit.png
Binary files differ
diff --git a/pidgin/pixmaps/protocols/48/mxit.png b/pidgin/pixmaps/protocols/48/mxit.png
new file mode 100644
index 0000000000..2b05a435a4
--- /dev/null
+++ b/pidgin/pixmaps/protocols/48/mxit.png
Binary files differ
diff --git a/pidgin/pixmaps/protocols/scalable/mxit.svg b/pidgin/pixmaps/protocols/scalable/mxit.svg
new file mode 100644
index 0000000000..3d9c0cc5ef
--- /dev/null
+++ b/pidgin/pixmaps/protocols/scalable/mxit.svg
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="48px" height="48px" viewBox="0 0 48 48" enable-background="new 0 0 48 48" xml:space="preserve">
+<image overflow="visible" width="34" height="39" xlink:href="data:image/jpeg;base64,/9j/4AAQSkZJRgABAgEASABIAAD/7AARRHVja3kAAQAEAAAAHgAA/+4AIUFkb2JlAGTAAAAAAQMA
+EAMCAwYAAAH0AAACSgAAA6n/2wCEABALCwsMCxAMDBAXDw0PFxsUEBAUGx8XFxcXFx8eFxoaGhoX
+Hh4jJSclIx4vLzMzLy9AQEBAQEBAQEBAQEBAQEABEQ8PERMRFRISFRQRFBEUGhQWFhQaJhoaHBoa
+JjAjHh4eHiMwKy4nJycuKzU1MDA1NUBAP0BAQEBAQEBAQEBAQP/CABEIACgAIwMBIgACEQEDEQH/
+xACyAAEAAwEBAAAAAAAAAAAAAAAAAQMFBAIBAAMBAQAAAAAAAAAAAAAAAAABAgMEEAABBAECBgMB
+AAAAAAAAAAABAAIDBAUREyESIhQVRRAjNQYRAAEDAgMDBwgLAAAAAAAAAAERAgMAEiETBDFRIkFh
+cUIUhcWhMlKDs9M01GKSIzNzwyRkhJQVEgABAwAGCQUAAAAAAAAAAAAAARECIUFRYYESEDFxodEy
+QoKicjNDNIT/2gAMAwEAAhEDEQAAANWbtDvwx51+eStam/NlFqUEyBo7eck8dAFoyr//2gAIAQIA
+AQUAe+RpEry7QJ4POA4FDeR3V9q//9oACAEDAAEFAAAUWjRDTTh8dC6V0r//2gAIAQEAAQUAiihn
+hNOsu1rBMr0HLks9hi7UdSE5ytyx5evM/J6dx6rCcInfp3ON2+CZfVY1z46RMm9IHyveZpW+q8Vc
+rtkr51qbF/QEx0s04eCg2//aAAgBAgIGPwBoxWSbRIyisXvNSfZsJrbFYkFblRlP1FOTHK+J8fid
+XuefE//aAAgBAwIGPwCmTDpJzsE9Tkrzs0VlXLuP/9oACAEBAQY/AIZ54GanU6mJk8sksTZnEyNa
+9Be11rW3IAMK+ChH8SP3dY6KH+pH7uvhNORy2wxscOhzGtc01n9rmvzP85bup2zs2b+JZ1q0csgJ
+adHC3hRVMcR5SN1XWSIqLw7fr0GNa8KQ242oC7Aea4moCAFc14cd4FqV3n4lWjP7KL2cNXfT/Kph
+3ZPtDUL0Nrbmk7i61K7z8SrQyNAd+khaQSm2OPmO6s5G3XXWrgllm1KLyjCjbUN2LSXLsFBha0BW
+kkOJ80g7Leau8vEqy9HPEdO0/ZRTxOe6NvoNeyWPhHIow31wt0rvVS/MVjHph6qX5iuKXTQr1mwv
+Lhzi+dwXpFZWdNZk5SX9fMzs/Z95fivkr//Z" transform="matrix(0.9999 0 0 0.9999 7.0146 6.0142)">
+</image>
+</svg>
diff --git a/pidgin/plugins/disco/gtkdisco.c b/pidgin/plugins/disco/gtkdisco.c
index e67043d960..320b49541e 100644
--- a/pidgin/plugins/disco/gtkdisco.c
+++ b/pidgin/plugins/disco/gtkdisco.c
@@ -141,8 +141,18 @@ static void pidgin_disco_create_tree(PidginDiscoList *pdl);
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 register_button_cb(GtkWidget *unused, PidginDiscoDialog *dialog)
static void discolist_cancel_cb(PidginDiscoList *pdl, const char *server)
{
+ pdl->dialog->prompt_handle = NULL;
+
pidgin_disco_list_set_in_progress(pdl, FALSE);
pidgin_disco_list_unref(pdl);
}
static void discolist_ok_cb(PidginDiscoList *pdl, const char *server)
{
+ pdl->dialog->prompt_handle = NULL;
gtk_widget_set_sensitive(pdl->dialog->browse_button, TRUE);
if (!server || !*server) {
@@ -226,7 +239,7 @@ static void browse_button_cb(GtkWidget *button, PidginDiscoDialog *dialog)
/* Note to translators: The string "Enter an XMPP Server" is asking the
user to type the name of an XMPP server which will then be queried */
- purple_request_input(my_plugin, _("Server name request"), _("Enter an XMPP Server"),
+ dialog->prompt_handle = purple_request_input(my_plugin, _("Server name request"), _("Enter an XMPP Server"),
_("Select an XMPP server to query"),
server, FALSE, FALSE, NULL,
_("Find Services"), PURPLE_CALLBACK(discolist_ok_cb),
@@ -380,6 +393,9 @@ destroy_win_cb(GtkWidget *window, gpointer d)
PidginDiscoDialog *dialog = d;
PidginDiscoList *list = dialog->discolist;
+ if (dialog->prompt_handle)
+ purple_request_close(PURPLE_REQUEST_INPUT, dialog->prompt_handle);
+
if (list) {
list->dialog = NULL;
diff --git a/pidgin/plugins/disco/gtkdisco.h b/pidgin/plugins/disco/gtkdisco.h
index 8f2fa5b20b..0c1aaba73a 100644
--- a/pidgin/plugins/disco/gtkdisco.h
+++ b/pidgin/plugins/disco/gtkdisco.h
@@ -43,6 +43,8 @@ struct _PidginDiscoDialog {
PurpleAccount *account;
PidginDiscoList *discolist;
+
+ gpointer *prompt_handle;
};
struct _PidginDiscoList {
diff --git a/pidgin/plugins/perl/common/Makefile.mingw b/pidgin/plugins/perl/common/Makefile.mingw
index 70fad73fe5..719af0f62c 100644
--- a/pidgin/plugins/perl/common/Makefile.mingw
+++ b/pidgin/plugins/perl/common/Makefile.mingw
@@ -5,9 +5,12 @@
#
PIDGIN_TREE_TOP := ../../../..
-GCCWARNINGS := -Wno-comment -Waggregate-return -Wcast-align -Wdeclaration-after-statement -Werror-implicit-function-declaration -Wextra -Wno-sign-compare -Wno-unused-parameter -Winit-self -Wmissing-declarations -Wmissing-prototypes -Wpointer-arith -Wundef -Wno-unused
include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
+GCCWARNINGS += -Wno-comment -Wno-unused -Wno-nested-externs
+
+DEFINES := $(subst -DWIN32_LEAN_AND_MEAN,,$(DEFINES))
+
TARGET = Pidgin
EXTUTILS ?= C:/perl/lib/ExtUtils
diff --git a/pidgin/plugins/win32/winprefs/Makefile.mingw b/pidgin/plugins/win32/winprefs/Makefile.mingw
index 0ab97fb5ff..9095458654 100644
--- a/pidgin/plugins/win32/winprefs/Makefile.mingw
+++ b/pidgin/plugins/win32/winprefs/Makefile.mingw
@@ -8,6 +8,7 @@ PIDGIN_TREE_TOP := ../../../..
include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
TARGET = winprefs
+DEFINES := $(subst -DWIN32_LEAN_AND_MEAN,,$(DEFINES))
DEFINES += -DWINVER=0x500
##
diff --git a/pidgin/plugins/xmppconsole.c b/pidgin/plugins/xmppconsole.c
index 19aedd44a4..10ecd8f891 100644
--- a/pidgin/plugins/xmppconsole.c
+++ b/pidgin/plugins/xmppconsole.c
@@ -660,7 +660,6 @@ signed_off_cb(PurpleConnection *gc)
gtk_combo_box_remove_text(GTK_COMBO_BOX(console->dropdown), i);
console->accounts = g_list_remove(console->accounts, gc);
- printf("%s\n", purple_account_get_username(gc->account));
console->count--;
if (gc == console->gc) {
diff --git a/pidgin/win32/nsis/pidgin-installer.nsi b/pidgin/win32/nsis/pidgin-installer.nsi
index 380a7847db..09d5e38100 100644
--- a/pidgin/win32/nsis/pidgin-installer.nsi
+++ b/pidgin/win32/nsis/pidgin-installer.nsi
@@ -717,6 +717,7 @@ Section Uninstall
Delete "$INSTDIR\ca-certs\AOL_Member_CA.pem"
Delete "$INSTDIR\ca-certs\CAcert_Class3.pem"
Delete "$INSTDIR\ca-certs\CAcert_Root.pem"
+ Delete "$INSTDIR\ca-certs\Entrust.net_Secure_Server_CA.pem"
Delete "$INSTDIR\ca-certs\Equifax_Secure_CA.pem"
Delete "$INSTDIR\ca-certs\Equifax_Secure_Global_eBusiness_CA-1.pem"
Delete "$INSTDIR\ca-certs\GTE_CyberTrust_Global_Root.pem"
diff --git a/po/ChangeLog b/po/ChangeLog
index 8c22f3d586..04d65ece3f 100644
--- a/po/ChangeLog
+++ b/po/ChangeLog
@@ -1,5 +1,11 @@
Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
+version 2.6.4
+ * Vietnamese translation updated (Clytie Siddall)
+
+version 2.6.3
+ * No changes
+
version 2.6.2
* Afrikaans translation updated (Friedel Wolff)
* Albanian translation updated (Besnik Bleta)
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 7729e2d457..6533d3ec21 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -126,6 +126,15 @@ libpurple/protocols/msnp9/session.c
libpurple/protocols/msnp9/state.c
libpurple/protocols/msnp9/switchboard.c
libpurple/protocols/msnp9/userlist.c
+libpurple/protocols/mxit/actions.c
+libpurple/protocols/mxit/filexfer.c
+libpurple/protocols/mxit/http.c
+libpurple/protocols/mxit/login.c
+libpurple/protocols/mxit/mxit.c
+libpurple/protocols/mxit/profile.c
+libpurple/protocols/mxit/protocol.c
+libpurple/protocols/mxit/roster.c
+libpurple/protocols/mxit/splashscreen.c
libpurple/protocols/myspace/myspace.c
libpurple/protocols/myspace/user.c
libpurple/protocols/myspace/zap.c
diff --git a/po/ca.po b/po/ca.po
index f26acd85d9..c099a3dd68 100644
--- a/po/ca.po
+++ b/po/ca.po
@@ -33,8 +33,8 @@ msgid ""
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 @@ msgstr ""
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 @@ msgstr ""
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 @@ msgstr "Habilita el registre"
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 @@ msgid_plural "List of %d users:\n"
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 @@ msgstr "Convida"
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 @@ msgstr ""
"\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 @@ msgstr "TinyURL"
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 @@ msgstr "Amics"
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."
+msgstr "No es pot comprovar el certificat 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."
+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."
-#, fuzzy
msgid "The certificate is not valid yet."
-msgstr "La cadena de certificació que presenta %s no és vàlida."
+msgstr "El certificat encara no és vàlid."
-#, fuzzy
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 @@ msgstr ""
"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."
+msgstr "La cadena de certificació que s'ha presentat no és vàlida."
-#, fuzzy
msgid "The certificate has been revoked."
-msgstr "Ha finalitzat la trucada."
+msgstr "El certificat ha estat revocat."
-#, fuzzy
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 @@ msgstr "Verificació d'un certificat SSL"
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"
+msgstr "No s'ha pogut certificar"
-#, fuzzy, c-format
+#, 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 @@ msgid "Resolver process exited without answering our request"
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 @@ msgstr "S'ha completat la transferència del fitxer %s"
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 @@ msgid ""
"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 ""
+"No hi ha cap més còdec. Les preferències dels còdecs al fitxer fs-codecs."
+"conf són massa estrictes."
-#, fuzzy
msgid "A non-recoverable Farsight2 error has occurred."
-msgstr "Hi ha hagut un error de connexió desconegut: %s."
+msgstr "S'ha produït un error no recuperable del Farsight2."
-#, fuzzy
-msgid "Conference error."
-msgstr "Conferència tancada"
+msgid "Conference error"
+msgstr "Error en la conferència"
-msgid "Error with your microphone."
-msgstr ""
+msgid "Error with your microphone"
+msgstr "S'ha produït un error amb el micròfon"
-msgid "Error with your webcam."
-msgstr ""
+msgid "Error with your webcam"
+msgstr "S'ha produït un error amb la càmera web"
-#, fuzzy, c-format
+#, 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 @@ msgstr "Connector de proves per a servidor d'IPC."
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 @@ msgstr "Foto"
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 @@ msgstr "Torna a demanar l'autorització"
msgid "Unsubscribe"
msgstr "Cancel·la la subscripció"
+msgid "Initiate _Chat"
+msgstr "Inicia un _xat"
+
msgid "Log In"
msgstr "Connecta"
@@ -4580,7 +4593,7 @@ msgstr "config: configura la sala de xat."
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 @@ msgstr ""
"affiliate &lt;owner|admin|member|outcast|none&gt; [sobrenom1] "
"[sobrenom2] ...: obtén els usuaris amb una afiliació, o els l'estableix."
-#, fuzzy
msgid ""
"role &lt;moderator|participant|visitor|none&gt; [nick1] [nick2] ...: Get the "
"users with a role or set users' role with the room."
msgstr ""
-"role &lt;usuari&gt; &lt;moderator|participant|visitor|none&gt; [sobrenom1] "
-"[sobrenom2] ...: obtén els usuaris amb el rol especificat, o els l'estableix."
+"role &lt;moderator|participant|visitor|none&gt; [sobrenom1] [sobrenom2] ...: "
+"obtén els usuaris amb el rol especificat, o els l'estableix."
msgid "invite &lt;user&gt; [message]: Invite a user to the room."
msgstr "invite &lt;usuari&gt; [sala]: convida un usuari a la sala."
@@ -5025,7 +5037,6 @@ msgstr "Fitxer d'amics incorrecte"
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 @@ msgstr "Obre la safata d'entrada de Hotmail"
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 @@ msgstr ""
"%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 @@ msgid "Server port"
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 @@ msgstr ""
#. 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 @@ msgstr ""
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 @@ msgstr "Edita el comentari sobre l'amic"
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 @@ msgstr "%d ha refusat el fitxer %s"
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 @@ msgid "Ignore conference and chatroom invitations"
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 @@ msgstr ""
msgid "Ignore buddy?"
msgstr "Voleu ignorar l'amic?"
-#, fuzzy
msgid "Invalid username or password"
-msgstr "El sobrenom o la contrasenya no són correctes"
+msgstr "El sobrenom o la contrasenya no són vàlides"
-#, fuzzy
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 @@ msgstr "Entra a un xat d'un usuari..."
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 @@ msgstr "El color de fons de la llista d'amics"
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 @@ msgstr "Text informatiu quan un contacte s'expandeix"
#. 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 @@ msgstr "Text informatiu per quan un amic estigui en línia"
#. 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"
+msgstr "Text fora de línia"
-#, fuzzy
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 @@ msgstr "Text informatiu per quan un amic tingui un missatge per llegir"
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 @@ msgstr "Àl_ies:"
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 @@ msgstr "/_Conversa"
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 @@ msgid "Estonian"
msgstr "Estonià"
msgid "Basque"
-msgstr ""
+msgstr "Basc"
msgid "Persian"
msgstr "Persa"
@@ -11728,6 +11737,13 @@ msgid ""
"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 @@ msgid ""
"Usage: %s [OPTION]...\n"
"\n"
msgstr ""
+"Forma d'ús: %s [OPCIÓ]...\n"
+"\n"
-#, fuzzy
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 @@ msgid "%s wishes to start a video session with you."
msgstr "%s vol iniciar una sessió de vídeo."
msgid "Incoming Call"
-msgstr ""
+msgstr "Trucada entrant"
msgid "_Pause"
msgstr "_Pausa"
@@ -12576,50 +12595,54 @@ msgstr "_Recurrent"
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 @@ msgstr ""
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 @@ msgstr "Port _final:"
msgid "Relay Server (TURN)"
msgstr "Servidor repetidor (TURN)"
-#, fuzzy
msgid "_TURN server:"
-msgstr "Servidor ST_UN:"
+msgstr "Servidor _TURN:"
msgid "Proxy Server &amp; Browser"
msgstr "Servidor intermediari i navegador"
@@ -14372,35 +14396,29 @@ msgstr ""
"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"
+msgstr "Àudio"
-#, fuzzy
msgid "Video"
msgstr " Vídeo"
msgid "Output"
-msgstr ""
+msgstr "Sortida"
-#, fuzzy
msgid "_Plugin"
-msgstr "Connectors"
+msgstr "_Connectors"
-#, fuzzy
msgid "_Device"
-msgstr "Dispositiu"
+msgstr "_Dispositiu"
msgid "Input"
-msgstr ""
+msgstr "Entrada"
-#, fuzzy
msgid "P_lugin"
-msgstr "Connectors"
+msgstr "C_onnectors"
-#, fuzzy
msgid "D_evice"
-msgstr "Dispositiu"
+msgstr "D_ispositiu"
#. *< magic
#. *< major version
@@ -14411,18 +14429,19 @@ msgstr "Dispositiu"
#. *< 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 @@ msgstr ""
"\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 @@ msgstr "Inicialització"
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 @@ msgstr "Envia i rep blocs XMPP en brut."
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 @@ msgstr "Aquest connector és útil per a depurar servidors i clients XMPP."
#~ 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"
diff --git a/po/de.po b/po/de.po
index 672450c3ac..11e9c1feed 100644
--- a/po/de.po
+++ b/po/de.po
@@ -11,8 +11,8 @@ msgid ""
msgstr ""
"Project-Id-Version: de\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2009-09-16 11:43+0200\n"
-"PO-Revision-Date: 2009-09-16 11:37+0200\n"
+"POT-Creation-Date: 2009-10-07 22:35+0200\n"
+"PO-Revision-Date: 2009-10-07 22:34+0200\n"
"Last-Translator: Bjoern Voigt <bjoern@cs.tu-berlin.de>\n"
"Language-Team: Deutsch <de@li.org>\n"
"MIME-Version: 1.0\n"
@@ -951,6 +951,9 @@ msgstr "Einladen"
msgid "(none)"
msgstr "(kein)"
+#. 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"
@@ -1041,10 +1044,10 @@ msgid "Pounce When Buddy..."
msgstr "Alarm, wenn Buddy..."
msgid "Signs on"
-msgstr "sich anmeldet"
+msgstr "sich angemeldet"
msgid "Signs off"
-msgstr "sich abmeldet"
+msgstr "sich abgemeldet"
msgid "Goes away"
msgstr "hinausgeht"
@@ -1461,7 +1464,7 @@ msgid "%s sent a message in %s"
msgstr "%s hat eine Nachricht in %s gesendet"
msgid "Buddy signs on/off"
-msgstr "Buddy sich an/abmeldet"
+msgstr "Buddy hat sich an- oder abgemeldet"
msgid "You receive an IM"
msgstr "Sie empfangen einen Nachricht"
@@ -1526,6 +1529,13 @@ msgstr ""
"\n"
"Hole TinyURL..."
+#, c-format
+msgid "TinyURL for above: %s"
+msgstr "TinyURL für oben: %s"
+
+msgid "Please wait while TinyURL fetches a shorter URL ..."
+msgstr "Bitte warten Sie, während TinyURL eine kürzere URL holt ..."
+
msgid "Only create TinyURL for URLs of this length or greater"
msgstr "TinyURL nur für URLs mit mindestens dieser Länge generieren"
@@ -1885,6 +1895,10 @@ msgid "Resolver process exited without answering our request"
msgstr "Auflösungsprozess hat sich beendet ohne die Anfrage zu beantworten"
#, c-format
+msgid "Error converting %s to punycode: %d"
+msgstr "Fehler beim Konvertieren von %s zu Punycode: %d"
+
+#, c-format
msgid "Thread creation failure: %s"
msgstr "Fehler beim Erzeugen eines Threads: %s"
@@ -2898,7 +2912,7 @@ msgid "Buddy Goes _Idle"
msgstr "Buddy _untätig wird"
msgid "Buddy _Signs On/Off"
-msgstr "Buddy _sich an/abmeldet"
+msgstr "Buddy hat_sich an- oder abgemeldet"
#. *< type
#. *< ui_requirement
@@ -5179,7 +5193,7 @@ msgstr ""
#, 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 ""
"Konnte den Buddy %s nicht hinzufügen, da der Benutzername ungültig ist. "
"Benutzernamen müssen gültige E-Mail-Adressen sein."
@@ -9708,6 +9722,17 @@ msgstr "Einen Benutzer zum Chat einladen..."
msgid "Open Inbox"
msgstr "Öffne Posteingang"
+msgid "Can't send SMS. Unable to obtain mobile carrier."
+msgstr ""
+"Die SMS kann nicht gesendet werden. Es konnte kein Mobilfunkkanal erreicht "
+"werden."
+
+msgid "Can't send SMS. Unknown mobile carrier."
+msgstr "Die SMS kann nicht gesendet werden. Unbekannter Mobilfunkkanal."
+
+msgid "Getting mobile carrier to send the SMS."
+msgstr "Hole einen Mobilfunkkanal zum Senden der SMS."
+
#. Write a local message to this conversation showing that a request for a
#. * Doodle session has been made
#.
@@ -12376,7 +12401,7 @@ msgid "Dismiss"
msgstr "Verwerfen"
msgid "<span weight=\"bold\" size=\"larger\">You have pounced!</span>"
-msgstr "<span weight=\"bold\" size=\"larger\">Sie haben geklopft!</span>"
+msgstr "<span weight=\"bold\" size=\"larger\">Sie wurden angestoßen!</span>"
msgid "The following plugins will be unloaded."
msgstr "Die folgenden Plugins werden entladen."
@@ -12440,10 +12465,10 @@ msgid "_Buddy name:"
msgstr "Budd_y-Name:"
msgid "Si_gns on"
-msgstr "si_ch anmeldet"
+msgstr "si_ch angemeldet"
msgid "Signs o_ff"
-msgstr "sich abmel_det"
+msgstr "sich abgemel_det"
msgid "Goes a_way"
msgstr "hinausgeh_t"
@@ -12509,7 +12534,7 @@ msgid "Paused while typing"
msgstr "Hat beim Tippen angehalten"
msgid "Signed on"
-msgstr "Hat sich anmeldet"
+msgstr "Hat sich angemeldet"
msgid "Returned from being idle"
msgstr "Ist nicht mehr inaktiv"
@@ -12521,7 +12546,7 @@ msgid "Stopped typing"
msgstr "Hat das Tippen gestoppt"
msgid "Signed off"
-msgstr "Hat sich abmeldet"
+msgstr "Hat sich abgemeldet"
msgid "Became idle"
msgstr "Wurde untätig"
@@ -12535,6 +12560,21 @@ msgstr "Eine Nachricht senden"
msgid "Unknown.... Please report this!"
msgstr "Unbekannt.... Bitte berichten Sie dieses Problem!"
+msgid "(Custom)"
+msgstr "(Benutzerdefiniert)"
+
+msgid "(Default)"
+msgstr "(Standard)"
+
+msgid "The default Pidgin sound theme"
+msgstr "Das Standard-Klangthema für Pidgin"
+
+msgid "The default Pidgin buddy list theme"
+msgstr "Das Standard-Buddy-Listen-Thema für Pidgin"
+
+msgid "The default Pidgin status icon theme"
+msgstr "Das Standard-Status-Icon-Thema für Pidgin"
+
msgid "Theme failed to unpack."
msgstr "Thema konnte nicht entpackt werden."
@@ -14381,9 +14421,6 @@ msgstr ""
"\n"
"* Hinweis: Dieses Plugin verlangt Win2000 oder höher."
-msgid "GTK+ Runtime Version"
-msgstr "GTK+ Runtime Version"
-
#. Autostart
msgid "Startup"
msgstr "Start"
@@ -14392,6 +14429,9 @@ msgstr "Start"
msgid "_Start %s on Windows startup"
msgstr "_Starte %s beim Windows-Start"
+msgid "Allow multiple instances"
+msgstr "Mehrere Instanzen erlauben"
+
msgid "_Dockable Buddy List"
msgstr "An_dockbare Buddy-Liste"
diff --git a/po/vi.po b/po/vi.po
index afe4d93dfb..08b392292a 100644
--- a/po/vi.po
+++ b/po/vi.po
@@ -7,20 +7,20 @@
# Trinh Minh Thanh <tmthanh@yahoo.com>.
# Nguyễn Thái Ngọc Duy <pclouds@users.sf.net>.
# Nguyễn Xuân Nguyên <xxxnnn@gmail.com>, 2007.
-# Clytie Siddall <clytie@riverland.net.au>, 2007-2008.
+# Clytie Siddall <clytie@riverland.net.au>, 2007-2009.
msgid ""
msgstr ""
"Project-Id-Version: CVS Version of Pidgin\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2009-09-07 18:26-0700\n"
-"PO-Revision-Date: 2008-06-22 21:58+0930\n"
+"POT-Creation-Date: 2009-10-03 22:46-0700\n"
+"PO-Revision-Date: 2009-09-30 22:16+0930\n"
"Last-Translator: Clytie Siddall <clytie@riverland.net.au>\n"
"Language-Team: Vietnamese <vi-VN@googlegroups.com>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
-"X-Generator: LocFactoryEditor 1.7b3\n"
+"X-Generator: LocFactoryEditor 1.8\n"
#. Translators may want to transliterate the name.
#. It is not to be translated.
@@ -31,7 +31,7 @@ msgstr "Finch"
msgid "%s. Try `%s -h' for more information.\n"
msgstr "%s. Chạy '%s -h' để biết thêm thông tin.\n"
-#, fuzzy, c-format
+#, c-format
msgid ""
"%s\n"
"Usage: %s [OPTION]...\n"
@@ -43,13 +43,13 @@ msgid ""
" -v, --version display the current version and exit\n"
msgstr ""
"%s\n"
-"Cách sử dụng: %s [TÙY_CHỌN]...\n"
+"Sử dụng: %s [TÙY_CHỌN]...\n"
"\n"
" -c, --config=THƯ_MỤC dùng thư mục này cho các tập tin cấu hình\n"
-" -d, --debug in các thông điệp gỡ lỗi ra thiết bị xuất chuẩn\n"
-" -h, --help hiện trợ giúp này rồi thoát\n"
-" -n, --nologin đừng tự động đăng nhập\n"
-" -v, --version hiện phiên bản hiện thời rồi thoát\n"
+" -d, --debug ra các thông điệp gỡ lỗi ra đầu lỗi tiêu chuẩn\n"
+" -h, --help hiển thị trợ giúp này, sau đó thoát\n"
+" -n, --nologin không tự động đăng nhập\n"
+" -v, --version hiển thị phiên bản hiện thời, sau đó thoát\n"
#, c-format
msgid ""
@@ -76,9 +76,8 @@ msgstr "Thông báo thư mới"
msgid "Remember password"
msgstr "Ghi nhớ mật khẩu"
-#, fuzzy
msgid "There are no protocol plugins installed."
-msgstr "Chưa cài đặt phần bổ sung giao thức."
+msgstr "Chưa cài đặt phần bổ sung giao thức nào."
msgid "(You probably forgot to 'make install'.)"
msgstr "(Rất có thể là bạn quên chạy lệnh « make install ».)"
@@ -142,17 +141,17 @@ msgstr "Sửa"
#, c-format
msgid "%s%s%s%s has made %s his or her buddy%s%s"
-msgstr "%s%s%s%s đã đặt %s là bạn thân của họ%s%s"
+msgstr "%s%s%s%s đã đặt %s là bạn chát của họ%s%s"
msgid "Add buddy to your list?"
-msgstr "Có thêm bạn thân vào danh sách của bạn không?"
+msgstr "Có thêm bạn chát vào danh sách của bạn không?"
#, c-format
msgid "%s%s%s%s wants to add %s to his or her buddy list%s%s"
msgstr "%s%s%s%s muốn thêm %s vào danh sách bạn bè của họ%s%s"
msgid "Authorize buddy?"
-msgstr "Cho phép bạn thân không?"
+msgstr "Cho phép bạn chát không?"
msgid "Authorize"
msgstr "Cho phép"
@@ -184,7 +183,7 @@ msgid "Default"
msgstr "Mặc định"
msgid "You must provide a username for the buddy."
-msgstr "Bạn cần phải cung cấp một tên người dùng cho bạn thân."
+msgstr "Bạn cần phải cung cấp một tên người dùng cho bạn chát."
msgid "You must provide a group."
msgstr "Bạn cần phải cung cấp một nhóm."
@@ -196,7 +195,7 @@ msgid "The selected account is not online."
msgstr "Tài khoản đã chọn không phải hiện thời trực tuyến."
msgid "Error adding buddy"
-msgstr "Lỗi thêm bạn thân"
+msgstr "Lỗi thêm bạn chát"
msgid "Username"
msgstr "Tên đăng nhập"
@@ -211,10 +210,10 @@ msgid "Account"
msgstr "Tài khoản"
msgid "Add Buddy"
-msgstr "Thêm bạn thân"
+msgstr "Thêm bạn chát"
msgid "Please enter buddy information."
-msgstr "Hãy nhập thông tin về bạn thân."
+msgstr "Hãy nhập thông tin về bạn chát."
msgid "Chats"
msgstr "Cuộc Chat"
@@ -272,7 +271,7 @@ msgid "Get Info"
msgstr "Lấy thông tin"
msgid "Add Buddy Pounce"
-msgstr "Thêm thông báo bạn thân"
+msgstr "Thêm thông báo bạn chát"
msgid "Send File"
msgstr "Gửi tập tin"
@@ -297,10 +296,10 @@ msgid "Enter empty string to reset the name."
msgstr "Gõ chuỗi rỗng để đặt lại tên."
msgid "Removing this contact will also remove all the buddies in the contact"
-msgstr "Gỡ bỏ liên lạc này thì cũng gỡ bỏ mọi bạn thân trong liên lạc"
+msgstr "Gỡ bỏ liên lạc này thì cũng gỡ bỏ mọi bạn chát trong liên lạc"
msgid "Removing this group will also remove all the buddies in the group"
-msgstr "Gỡ bỏ nhóm này thì cũng gỡ bỏ mọi bạn thân trong nhóm"
+msgstr "Gỡ bỏ nhóm này thì cũng gỡ bỏ mọi bạn chát trong nhóm"
#, c-format
msgid "Are you sure you want to remove %s?"
@@ -421,7 +420,7 @@ msgid "Empty groups"
msgstr "Nhóm rỗng"
msgid "Offline buddies"
-msgstr "Bạn thân ngoại tuyến"
+msgstr "Bạn chát ngoại tuyến"
msgid "Sort"
msgstr "Sắp xếp"
@@ -621,7 +620,7 @@ msgid "Show Timestamps"
msgstr "Hiện nhãn thời gian"
msgid "Add Buddy Pounce..."
-msgstr "Thêm thông báo bạn thân..."
+msgstr "Thêm thông báo bạn chát..."
msgid "Invite..."
msgstr "Mời..."
@@ -635,13 +634,16 @@ msgstr "Bật âm thanh"
msgid "<AUTO-REPLY> "
msgstr "<TỰ_ĐỘNG_ĐÁP_ỨNG>"
-#, fuzzy, c-format
+#, c-format
msgid "List of %d user:\n"
msgid_plural "List of %d users:\n"
-msgstr[0] "Danh sách người dùng:\n"
+msgstr[0] "Danh sách %d người dùng:\n"
-msgid "Supported debug options are: version"
-msgstr "Tùy chọn gỡ lỗi được hỗ trợ : phiên bản"
+msgid "Supported debug options are: plugins version"
+msgstr ""
+"Tùy chọn gỡ lỗi được hỗ trợ :\n"
+" • plugins\t\tcác phần bổ sung\n"
+" • version\t\tphiên bản"
msgid "No such command (in this context)."
msgstr "Không có lệnh nào như vậy (trong ngữ cảnh này)."
@@ -676,7 +678,7 @@ msgstr ""
msgid "me &lt;action&gt;: Send an IRC style action to a buddy or chat."
msgstr ""
-"me &lt;hành động&gt;: Gởi một hoạt động kiểu IRC đến bạn thân hay chát."
+"me &lt;hành động&gt;: Gởi một hoạt động kiểu IRC đến bạn chát hay chát."
msgid ""
"debug &lt;option&gt;: Send various debug information to the current "
@@ -868,12 +870,11 @@ msgstr "Mọi cuộc trao đổi"
msgid "System Log"
msgstr "Sổ theo dõi hệ thống"
-#, fuzzy
msgid "Calling..."
-msgstr "Đang tính toán..."
+msgstr "Đang gọi..."
msgid "Hangup"
-msgstr ""
+msgstr "Ngừng nói"
#. Number of actions
msgid "Accept"
@@ -883,25 +884,25 @@ msgid "Reject"
msgstr "Từ chối"
msgid "Call in progress."
-msgstr ""
+msgstr "Cuộc gọi đang chạy."
msgid "The call has been terminated."
-msgstr ""
+msgstr "Cuộc gọi đã bị chấm dứt."
#, c-format
msgid "%s wishes to start an audio session with you."
-msgstr ""
+msgstr "%s muốn bắt đầu một buổi hợp tiếng nói với bạn."
#, c-format
msgid "%s is trying to start an unsupported media session type with you."
msgstr ""
+"%s đang thử bắt đầu với bạn một buổi hợp kiểu phương tiện không được hỗ trợ."
-#, fuzzy
msgid "You have rejected the call."
-msgstr "Bạn rời khỏi kênh %s%s"
+msgstr "Bạn đã từ chối cuộc gọi."
msgid "call: Make an audio call."
-msgstr ""
+msgstr "gọi: Gọi nói tiếng cho ai đó."
msgid "Emails"
msgstr "Thư điện tử"
@@ -928,7 +929,7 @@ msgid "Info for %s"
msgstr "Thông tin cho %s"
msgid "Buddy Information"
-msgstr "Thông tin bạn thân"
+msgstr "Thông tin bạn chát"
msgid "Continue"
msgstr "Tiếp tục"
@@ -942,6 +943,9 @@ msgstr "Mời"
msgid "(none)"
msgstr "(không có)"
+#. 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"
@@ -1007,13 +1011,13 @@ msgid "Preferences"
msgstr "Tùy thích"
msgid "Please enter a buddy to pounce."
-msgstr "Hãy nhập một bạn thân để thông báo."
+msgstr "Hãy nhập một bạn chát để thông báo."
msgid "New Buddy Pounce"
-msgstr "Thông báo bạn thân mới"
+msgstr "Thông báo bạn chát mới"
msgid "Edit Buddy Pounce"
-msgstr "Sửa thông báo bạn thân"
+msgstr "Sửa thông báo bạn chát"
msgid "Pounce Who"
msgstr "Thông báo cho ai"
@@ -1027,7 +1031,7 @@ msgstr "Tên buddy:"
#. Create the "Pounce When Buddy..." frame.
msgid "Pounce When Buddy..."
-msgstr "Thông báo khi bạn thân..."
+msgstr "Thông báo khi bạn chát..."
msgid "Signs on"
msgstr "Đăng nhập"
@@ -1098,7 +1102,7 @@ msgid "Are you sure you want to delete the pounce on %s for %s?"
msgstr "Bạn chắc chắn muốn xoá thông báo trên %s cho %s không?"
msgid "Buddy Pounces"
-msgstr "Thông báo bạn thân"
+msgstr "Thông báo bạn chát"
#, c-format
msgid "%s has started typing to you (%s)"
@@ -1159,7 +1163,7 @@ msgid "Show Offline Buddies"
msgstr "Hiển thị bạn bè ngoại tuyến"
msgid "Notify buddies when you are typing"
-msgstr "Thông báo bạn thân mà bạn đang gõ tin nhẳn cho họ"
+msgstr "Thông báo bạn chát mà bạn đang gõ tin nhẳn cho họ"
msgid "Log format"
msgstr "Định dạng sổ theo dõi"
@@ -1220,10 +1224,10 @@ msgid "Room List"
msgstr "Danh sách phòng"
msgid "Buddy logs in"
-msgstr "Bạn thân đăng nhập"
+msgstr "Bạn chát đăng nhập"
msgid "Buddy logs out"
-msgstr "Bạn thân đăng xuất"
+msgstr "Bạn chát đăng xuất"
msgid "Message received"
msgstr "Nhận tin nhẳn"
@@ -1450,7 +1454,7 @@ msgid "%s sent a message in %s"
msgstr "%s đã gửi tin nhẳn trong %s"
msgid "Buddy signs on/off"
-msgstr "Bạn thân đăng nhập/xuất"
+msgstr "Bạn chát đăng nhập/xuất"
msgid "You receive an IM"
msgstr "Bạn nhận tin nhắn"
@@ -1513,22 +1517,31 @@ msgid ""
"\n"
"Fetching TinyURL..."
msgstr ""
+"\n"
+"Đang lấy TinyURL..."
-msgid "Only create TinyURL for URLs of this length or greater"
+#, c-format
+msgid "TinyURL for above: %s"
msgstr ""
-msgid "TinyURL (or other) address prefix"
+msgid "Please wait while TinyURL fetches a shorter URL ..."
msgstr ""
-#, fuzzy
+msgid "Only create TinyURL for URLs of this length or greater"
+msgstr "Chỉ tạo TinyURL cho địa chỉ URL có ít nhất chiều dài này"
+
+msgid "TinyURL (or other) address prefix"
+msgstr "Tiền tố địa chỉ TinyURL (hay kiểu khác)"
+
msgid "TinyURL"
-msgstr "URL điệu"
+msgstr "TinyURL"
msgid "TinyURL plugin"
-msgstr ""
+msgstr "Phần bổ sung TinyURL"
msgid "When receiving a message with URL(s), use TinyURL for easier copying"
msgstr ""
+"Khi nhận một tin nhẳn chứa địa chỉ URL, dùng TinyURL để sao chép dễ hơn"
msgid "Online"
msgstr "Trực tuyến"
@@ -1537,10 +1550,10 @@ msgid "Offline"
msgstr "Ngoại tuyến"
msgid "Online Buddies"
-msgstr "Bạn thân trực tuyến"
+msgstr "Bạn chát trực tuyến"
msgid "Offline Buddies"
-msgstr "Bạn thân ngoại tuyến"
+msgstr "Bạn chát ngoại tuyến"
msgid "Online/Offline"
msgstr "Trực/Ngoại tuyến"
@@ -1552,13 +1565,13 @@ msgid "No Grouping"
msgstr "Không theo nhóm"
msgid "Nested Subgroup"
-msgstr ""
+msgstr "Nhóm phụ lồng vào"
msgid "Nested Grouping (experimental)"
-msgstr ""
+msgstr "Nhóm lại lồng nhau (vẫn thực nghiệm"
msgid "Provides alternate buddylist grouping options."
-msgstr "Cung cấp thêm tùy chọn nhóm lại danh sách bạn thân."
+msgstr "Cung cấp thêm tùy chọn nhóm lại danh sách bạn chát."
# Name: don't translate/Tên: đừng dịch
msgid "Lastlog"
@@ -1636,28 +1649,22 @@ msgstr "Bạn bè"
msgid "buddy list"
msgstr "danh sách bạn bè"
-#, fuzzy
msgid "The certificate is self-signed and cannot be automatically checked."
-msgstr ""
-"« %s » cung cấp một chứng nhận tự ký. Không thể tự động kiểm tra chứng nhận "
-"như vậy."
+msgstr "Chứng nhận này tự ký thì không thể được tự động kiểm tra."
-#, fuzzy
msgid "The root certificate this one claims to be issued by is unknown."
-msgstr "Pidgin không nhận ra chứng nhận gốc có vẻ đã cấp chứng nhận này."
+msgstr "Không nhận ra chứng nhận gốc mà tùy theo chứng nhận này đã cấp nó."
-#, fuzzy
msgid "The certificate is not valid yet."
-msgstr "Dãy chứng nhận được cung cấp cho %s không phải là hợp lệ."
+msgstr "Chứng nhận này chưa hợp lệ."
-#, fuzzy
msgid "The certificate has expired and should not be considered valid."
-msgstr "Dãy chứng nhận được cung cấp cho %s không phải là hợp lệ."
+msgstr ""
+"Chứng nhận này đã hết hạn sử dụng thì không nên được thấy là vẫn hợp lệ."
#. Translators: "domain" refers to a DNS domain (e.g. talk.google.com)
-#, fuzzy
msgid "The certificate presented is not issued to this domain."
-msgstr "Dãy chứng nhận được cung cấp cho %s không phải là hợp lệ."
+msgstr "Chứng nhận đưa ra không phải được cấp cho miền này."
msgid ""
"You have no database of root certificates, so this certificate cannot be "
@@ -1666,20 +1673,17 @@ msgstr ""
"Bạn không có cơ sở dữ liệu chứng nhận gốc nên không thể thẩm tra chứng nhận "
"này."
-#, fuzzy
msgid "The certificate chain presented is invalid."
-msgstr "Dãy chứng nhận được cung cấp cho %s không phải là hợp lệ."
+msgstr "Dãy chứng nhận đưa ra không phải hợp lệ."
-#, fuzzy
msgid "The certificate has been revoked."
-msgstr "Dãy chứng nhận được cung cấp cho %s không phải là hợp lệ."
+msgstr "Chứng nhận đã bị hủy bỏ."
-#, fuzzy
msgid "An unknown certificate error occurred."
-msgstr "Lỗi đăng nhập không rõ : %s."
+msgstr "Gặp một lỗi chứng nhận không rõ."
msgid "(DOES NOT MATCH)"
-msgstr "(KHÔNG KHỚP)"
+msgstr "(KHÔNG TƯƠNG ỨNG)"
#. Make messages
#, c-format
@@ -1720,25 +1724,24 @@ msgstr "Thẩm tra chứng nhận SSL"
msgid "_View Certificate..."
msgstr "_Xem chứng nhận..."
-#, fuzzy, c-format
+#, c-format
msgid "The certificate for %s could not be validated."
-msgstr "Dãy chứng nhận được cung cấp cho %s không phải là hợp lệ."
+msgstr "Không thể thẩm tra được chứng nhận cho %s."
#. TODO: Probably wrong.
msgid "SSL Certificate Error"
msgstr "Lỗi chứng nhận SSL"
-#, fuzzy
msgid "Unable to validate certificate"
-msgstr "Không thể xác thực: %s"
+msgstr "Không thể thẩm tra chứng nhận"
-#, fuzzy, c-format
+#, 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 ""
-"« %s » cung cấp một chứng nhận có vẻ là theo dạng « %s » thay thế. Có thể "
-"nghĩa là bạn không phải có kết nối đến dịch vụ bạn tin là đó."
+"Chứng nhận tuyên bố là nó đến từ « %s » thay thế. Có thể nghĩa là bạn không "
+"phải có kết nối đến dịch vụ bạn tin là đó."
#. Make messages
#, c-format
@@ -1822,9 +1825,8 @@ msgstr "%s rời phòng."
msgid "%s left the room (%s)."
msgstr "%s rời phòng (%s)."
-#, fuzzy
msgid "Invite to chat"
-msgstr "Mời vào hội thảo"
+msgstr "Mời chát"
#. Put our happy label in it.
msgid ""
@@ -1878,7 +1880,11 @@ msgstr ""
#, c-format
msgid "Resolver process exited without answering our request"
-msgstr ""
+msgstr "Tiến trình giải quyết đã thoát mà không đáp ứng yêu cầu"
+
+#, c-format
+msgid "Error converting %s to punycode: %d"
+msgstr "Gặp lỗi khi chuyển đổi %s sang punycode: %d"
#, c-format
msgid "Thread creation failure: %s"
@@ -1962,31 +1968,31 @@ msgstr "Muốn gửi %s cho %s"
msgid "Starting transfer of %s from %s"
msgstr "Đang bắt đầu truyền %s từ %s"
-#, fuzzy, c-format
+#, c-format
msgid "Transfer of file <A HREF=\"file://%s\">%s</A> complete"
-msgstr "Đã truyền xong tập tin %s"
+msgstr "Đã truyền hoàn toàn tập tin <A HREF=\"file://%s\">%s</A>"
#, c-format
msgid "Transfer of file %s complete"
-msgstr "Đã truyền xong tập tin %s"
+msgstr "Đã truyền hoàn toàn tập tin %s"
msgid "File transfer complete"
-msgstr "Đã truyền xong tập tin"
+msgstr "Đã truyền hoàn toàn tập tin"
-#, fuzzy, c-format
+#, c-format
msgid "You cancelled the transfer of %s"
-msgstr "Bạn đã thôi truyền %s"
+msgstr "Bạn đã thôi tiến trình truyền %s"
msgid "File transfer cancelled"
msgstr "Tiến trình truyền tập tin bị thôi."
-#, fuzzy, c-format
+#, c-format
msgid "%s cancelled the transfer of %s"
-msgstr "%s đã thôi truyền %s"
+msgstr "%s đã thôi tiến trình truyền %s"
-#, fuzzy, c-format
+#, c-format
msgid "%s cancelled the file transfer"
-msgstr "%s đã thôi truyền tập tin"
+msgstr "%s đã thôi tiến trình truyền tập tin"
#, c-format
msgid "File transfer to %s failed."
@@ -2184,32 +2190,33 @@ msgid ""
"No codecs found. Install some GStreamer codecs found in GStreamer plugins "
"packages."
msgstr ""
+"Không tìm thấy codec nào. Hãy cài đặt một số codec GStreamer từ gói phần bổ "
+"sung GStreamer."
msgid ""
"No codecs left. Your codec preferences in fs-codecs.conf are too strict."
msgstr ""
+"Không có codec nào còn lại. Thiết lập codec trong tập tin cấu hình « fs-"
+"codecs.conf » vẫn quá bị hạn chế."
-#, fuzzy
msgid "A non-recoverable Farsight2 error has occurred."
-msgstr "Lỗi đăng nhập không rõ : %s."
+msgstr "Gặp một lỗi Farsight2 từ đó không thể phục hồi."
-#, fuzzy
-msgid "Conference error."
-msgstr "Hội thảo bị đóng"
+msgid "Conference error"
+msgstr "Lỗi hội thảo"
-msgid "Error with your microphone."
-msgstr ""
+msgid "Error with your microphone"
+msgstr "Gặp lỗi với cái máy vi âm"
-msgid "Error with your webcam."
-msgstr ""
+msgid "Error with your webcam"
+msgstr "Gặp lỗi với cái máy ảnh Web"
-#, fuzzy, c-format
+#, c-format
msgid "Error creating session: %s"
-msgstr "Lỗi khi tạo kết nối"
+msgstr "Gặp lỗi khi tạo buổi hợp: %s"
-#, fuzzy
msgid "Error creating conference."
-msgstr "Lỗi khi tạo kết nối"
+msgstr "Gặp lỗi khi tạo hội thảo."
#, c-format
msgid "You are using %s, but this plugin requires %s."
@@ -2310,9 +2317,8 @@ msgstr ""
"Tự mở thông báo khi việc truyền tập tin đã chấp nhận tự động đã chạy xong\n"
"(chỉ khi không có cuộc thoại với người gửi)"
-#, fuzzy
msgid "Create a new directory for each user"
-msgstr "Chọn một thư mục người dùng trong đó cần tìm kiếm"
+msgstr "Tạo một thư mục mới cho mỗi người dùng"
msgid "Notes"
msgstr "Ghi chú"
@@ -2332,16 +2338,16 @@ msgstr "Sửa ghi chú..."
#. *< priority
#. *< id
msgid "Buddy Notes"
-msgstr "Ghi chú bạn thân"
+msgstr "Ghi chú bạn chát"
#. *< name
#. *< version
msgid "Store notes on particular buddies."
-msgstr "Lưu bản ghi chú về bạn thân nào đó"
+msgstr "Lưu bản ghi chú về bạn chát nào đó"
#. *< summary
msgid "Adds the option to store notes for buddies on your buddy list."
-msgstr "Thêm tùy chọn để lưu bản ghi chú về bạn thân trên danh sách bạn bè."
+msgstr "Thêm tùy chọn để lưu bản ghi chú về bạn chát trên danh sách bạn bè."
#. *< type
#. *< ui_requirement
@@ -2468,17 +2474,17 @@ msgstr ""
"lệnh IPC."
msgid "Hide Joins/Parts"
-msgstr ""
+msgstr "Ẩn Vào/Rời"
#. Translators: Followed by an input request a number of people
msgid "For rooms with more than this many people"
-msgstr ""
+msgstr "Đối với phòng có nhiều hơn số các người này"
msgid "If user has not spoken in this many minutes"
-msgstr ""
+msgstr "Nếu người dùng không nói gì trong vòng số các phút này"
msgid "Apply hiding rules to buddies"
-msgstr ""
+msgstr "Áp dụng các quy tắc cho bạn bè"
#. *< type
#. *< ui_requirement
@@ -2618,7 +2624,6 @@ msgstr ""
"Bao gồm trong bộ xem bản ghi các bản ghi của ứng dụng khách tin nhắn khác."
#. * description
-#, fuzzy
msgid ""
"When viewing logs, this plugin will include logs from other IM clients. "
"Currently, this includes Adium, MSN Messenger, aMSN, and Trillian.\n"
@@ -2627,7 +2632,8 @@ msgid ""
"at your own risk!"
msgstr ""
"Khi xem bản ghi, phần bổ sung này sẽ cũng bao gồm các bản ghi của ứng dụng "
-"khách tin nhắn khác. Hiện thời, gồm có Adium, MSN Messenger, và Trillian.\n"
+"khách tin nhắn khác. Hiện thời, gồm có Adium, MSN Messenger, aMSN và "
+"Trillian.\n"
"\n"
"CẢNH BÁO : phần bổ sung này vẫn còn là mã nguồn anfa (rất mới, chưa thử) nên "
"có thể sụp đổ nhiều. Hãy tự chịu trách nhiệm khi dùng nó !"
@@ -2675,13 +2681,12 @@ msgstr "Mô phỏng tin nhẳn ngoại tuyến"
msgid "Save messages sent to an offline user as pounce."
msgstr "Lưu dạng thông báo các tin nhẳn được gửi cho người dùng chưa kết nối."
-#, fuzzy
msgid ""
"The rest of the messages will be saved as pounces. You can edit/delete the "
"pounce from the `Buddy Pounce' dialog."
msgstr ""
"Phần còn lại của các tin nhẳn sẽ được lưu dạng thông báo. Bạn cũng có thể "
-"chỉnh sửa/xoá thông báo trong hộp thoại « Thông báo bạn thân »."
+"sửa/xoá thông báo trong hộp thoại « Thông báo bạn chát »."
#, c-format
msgid ""
@@ -2696,7 +2701,7 @@ msgstr "Tin nhẳn ngoại tuyến"
msgid "You can edit/delete the pounce from the `Buddy Pounces' dialog"
msgstr ""
-"Bạn cũng có thể chỉnh sửa/xoá thông báo trong hộp thoại « Thông báo bạn thân "
+"Bạn cũng có thể chỉnh sửa/xoá thông báo trong hộp thoại « Thông báo bạn chát "
"»."
msgid "Yes"
@@ -2711,9 +2716,8 @@ msgstr "Lưu tin nhẳn ngoại tuyến trong thông báo"
msgid "Do not ask. Always save in pounce."
msgstr "Đừng hỏi. Luôn luôn lưu trong thông báo."
-#, fuzzy
msgid "One Time Password"
-msgstr "Nhập mật khẩu"
+msgstr "Mật khẩu Một lần"
#. *< type
#. *< ui_requirement
@@ -2722,13 +2726,13 @@ msgstr "Nhập mật khẩu"
#. *< priority
#. *< id
msgid "One Time Password Support"
-msgstr ""
+msgstr "Hỗ trợ Mật khẩu Một lần"
#. *< name
#. *< version
#. * summary
msgid "Enforce that passwords are used only once."
-msgstr ""
+msgstr "Buộc dùng mỗi mật khẩu chỉ một lần thôi"
#. * description
msgid ""
@@ -2736,6 +2740,10 @@ msgid ""
"are only used in a single successful connection.\n"
"Note: The account password must not be saved for this to work."
msgstr ""
+"Cho phép bạn ép buộc đặc trưng cho mỗi tài khoản rằng mật khẩu nào chưa lưu "
+"thì được sử dụng trong chỉ một kết nối thành công.\n"
+"Ghi chú : một khi lưu được, mật khẩu tài khoản không hợp tác với tuỳ chọn "
+"này."
#. *< type
#. *< ui_requirement
@@ -2889,13 +2897,13 @@ msgid "Notify When"
msgstr "Thông báo khi"
msgid "Buddy Goes _Away"
-msgstr "Bạn thân đi _vắng"
+msgstr "Bạn chát đi _vắng"
msgid "Buddy Goes _Idle"
-msgstr "Bạn thân mới _nghỉ:"
+msgstr "Bạn chát mới _nghỉ:"
msgid "Buddy _Signs On/Off"
-msgstr "Bạn thân đăn_g nhập/xuất"
+msgstr "Bạn chát đăn_g nhập/xuất"
#. *< type
#. *< ui_requirement
@@ -2904,7 +2912,7 @@ msgstr "Bạn thân đăn_g nhập/xuất"
#. *< priority
#. *< id
msgid "Buddy State Notification"
-msgstr "Thông báo trạng thái bạn thân"
+msgstr "Thông báo trạng thái bạn chát"
#. *< name
#. *< version
@@ -2915,7 +2923,7 @@ msgid ""
"idle."
msgstr ""
"Thông báo trong cửa sổ cuộc thoại trạng thái vắng mặt, có mặt, hay nghỉ của "
-"bạn thân."
+"bạn chát."
msgid "Tcl Plugin Loader"
msgstr "Bộ nạp phần bổ sung Tcl"
@@ -2930,17 +2938,15 @@ msgstr ""
"Không thể phát hiện bản cài đặt ActiveTCL. Muốn sử dụng phần bổ sung TCL thì "
"cài đặt ActiveTCL từ địa chỉ « http://www.activestate.com ».\n"
-#, fuzzy
msgid ""
"Unable to find Apple's \"Bonjour for Windows\" toolkit, see http://d.pidgin."
"im/BonjourWindows for more information."
msgstr ""
-"Không tìm thấy bộ công cụ Apple Bonjour For Windows, xem FAQ (Hỏi Đáp) ở địa "
-"chỉ « http://d.pidgin.im/BonjourWindows » để tìm chi tiết."
+"Không tìm thấy bộ công cụ Apple Bonjour For Windows, xem « http://d.pidgin.im/"
+"BonjourWindows » để tìm thêm thông tin."
-#, fuzzy
msgid "Unable to listen for incoming IM connections"
-msgstr "Không thể lắng nghe kết nối tin nhắn gửi đến\n"
+msgstr "Không thể lắng nghe kết nối tin nhắn gửi đến"
msgid ""
"Unable to establish connection with the local mDNS server. Is it running?"
@@ -2978,9 +2984,8 @@ msgid "Purple Person"
msgstr "Người Purple"
#. Creating the options for the protocol
-#, fuzzy
msgid "Local Port"
-msgstr "Nơi ở"
+msgstr "Cổng cục bộ"
# Đây là tên của giao thức Apple: đừng dịch.
msgid "Bonjour"
@@ -2993,21 +2998,17 @@ msgstr "%s đã đóng cửa sổ cuộc thoại."
msgid "Unable to send the message, the conversation couldn't be started."
msgstr "Không thể gửi tin, vì không thể bắt đầu nói chuyện."
-#, fuzzy, c-format
+#, c-format
msgid "Unable to create socket: %s"
-msgstr ""
-"Không thể tạo ổ cắm\n"
-"%s"
+msgstr "Không thể tạo ổ cắm: %s"
-#, fuzzy, c-format
+#, c-format
msgid "Unable to bind socket to port: %s"
-msgstr "Không thể đóng kết ổ cắm tới cổng"
+msgstr "Không thể đóng kết ổ cắm tới cổng: %s"
-#, fuzzy, c-format
+#, c-format
msgid "Unable to listen on socket: %s"
-msgstr ""
-"Không thể tạo ổ cắm\n"
-"%s"
+msgstr "Không thể lắng nghe trên ổ cắm: %s"
msgid "Error communicating with local mDNSResponder."
msgstr "Lỗi liên lạc với cơ chế đáp ứng DNS mDNSResponder cục bộ."
@@ -3055,17 +3056,14 @@ msgstr "Lưu danh sách bạn bè..."
msgid "Load buddylist from file..."
msgstr "Nạp danh sách bạn bè từ tập tin..."
-#, fuzzy
msgid "You must fill in all registration fields"
-msgstr "Đìên vào các trường đăng ký."
+msgstr "Phải điền vào tất cả các trường đăng ký"
-#, fuzzy
msgid "Passwords do not match"
-msgstr "Hai mật khẩu không trùng."
+msgstr "Hai mật khẩu không trùng nhau"
-#, fuzzy
msgid "Unable to register new account. An unknown error occurred."
-msgstr "Không thể đăng ký tài khoản mới. Lỗi xảy ra.\n"
+msgstr "Không thể đăng ký tài khoản mới. Gặp một lỗi không rõ."
msgid "New Gadu-Gadu Account Registered"
msgstr "Tài khoản Gadu-Gadu mới đã đăng ký"
@@ -3080,11 +3078,11 @@ msgid "Password (again)"
msgstr "Nhập lại mật khẩu"
msgid "Enter captcha text"
-msgstr ""
+msgstr "Gõ chuỗi Captcha"
-#, fuzzy
+# Tên: đừng dịch
msgid "Captcha"
-msgstr "Lưu ảnh"
+msgstr "Captcha"
msgid "Register New Gadu-Gadu Account"
msgstr "Đăng ký tài khoản Gadu-Gadu mới"
@@ -3154,7 +3152,7 @@ msgstr "Đổi mật khẩu Gadu-Gadu"
#, c-format
msgid "Select a chat for buddy: %s"
-msgstr "Chọn cuộc chát cho bạn thân: %s"
+msgstr "Chọn cuộc chát cho bạn chát: %s"
msgid "Add to chat..."
msgstr "Thêm vào chát..."
@@ -3223,9 +3221,9 @@ msgstr "Thêm vào chát"
msgid "Chat _name:"
msgstr "Tê_n chát:"
-#, fuzzy, c-format
+#, c-format
msgid "Unable to resolve hostname '%s': %s"
-msgstr "Không thể kết nối đến máy phục vụ."
+msgstr "Không thể quyết định tên máy « %s »: %s"
#. 1. connect to server
#. connect to the server
@@ -3238,9 +3236,8 @@ msgstr "Lỗi chát"
msgid "This chat name is already in use"
msgstr "Tên chát này đang được dùng"
-#, fuzzy
msgid "Not connected to the server"
-msgstr "Không phải có kết nối tới máy phục vụ."
+msgstr "Không phải có kết nối tới máy phục vụ"
msgid "Find buddies..."
msgstr "Tìm bạn bè..."
@@ -3282,9 +3279,8 @@ msgstr "Hệ tin nhắn nhanh phổ biến ở Phần Lan"
msgid "Gadu-Gadu User"
msgstr "Người dùng Gadu-Gadu"
-#, fuzzy
msgid "GG server"
-msgstr "Lập thông tin người dùng..."
+msgstr "Máy phục vụ GG"
#, c-format
msgid "Unknown command: %s"
@@ -3300,16 +3296,15 @@ msgstr "Chưa lập chủ đề"
msgid "File Transfer Failed"
msgstr "Lỗi truyền tập tin"
-#, fuzzy
msgid "Unable to open a listening port."
-msgstr "Không thể mở cổng lắng nghe."
+msgstr "Không thể mở một cổng lắng nghe."
# MOTD: Message Of The Day: thông điệp của hôm nay
msgid "Error displaying MOTD"
-msgstr "Lỗi hiển thị MOTD"
+msgstr "Lỗi hiển thị MOTD (thông điệp của hôm nay)"
msgid "No MOTD available"
-msgstr "MOTD hiện không có"
+msgstr "Không có sẵn MOTD (thông điệp của hôm nay)"
msgid "There is no MOTD associated with this connection."
msgstr "Không có MOTD (thông điệp của hôm nay) liên quan đến kết nối này."
@@ -3325,11 +3320,9 @@ msgstr "MOTD cho %s"
#.
#. TODO: what to do here - do we really have to disconnect?
#. TODO: do we really want to disconnect on a failure to write?
-#, fuzzy, c-format
+#, c-format
msgid "Lost connection with server: %s"
-msgstr ""
-"Mất kết nối với máy phục vụ :\n"
-"%s"
+msgstr "Mất kết nối với máy phục vụ : %s"
msgid "View MOTD"
msgstr "Xem MOTD"
@@ -3340,24 +3333,23 @@ msgstr "_Kênh:"
msgid "_Password:"
msgstr "_Mật khẩu :"
-#, fuzzy
msgid "IRC nick and server may not contain whitespace"
-msgstr "Tên hiệu cho IRC không được chứa dấu cách"
+msgstr "Tên hiệu và máy phục vụ cho IRC không được chứa dấu cách"
msgid "SSL support unavailable"
-msgstr "Hiện không có hỗ trợ SSL"
+msgstr "Hỗ trợ SSL không sẵn sàng"
msgid "Unable to connect"
msgstr "Không thể kết nối"
#. this is a regular connect, error out
-#, fuzzy, c-format
+#, c-format
msgid "Unable to connect: %s"
-msgstr "Không thể kết nối tới %s"
+msgstr "Không thể kết nối: %s"
-#, fuzzy, c-format
+#, c-format
msgid "Server closed the connection"
-msgstr "Máy phục vụ đã đóng kết nối."
+msgstr "Máy phục vụ đã đóng kết nối"
msgid "Users"
msgstr "Người dùng"
@@ -3392,7 +3384,7 @@ msgid "Encodings"
msgstr "Bảng mã"
msgid "Auto-detect incoming UTF-8"
-msgstr ""
+msgstr "Tự động phát hiện UTF-8 gửi đến"
msgid "Real name"
msgstr "Tên thật"
@@ -3407,9 +3399,9 @@ msgstr "Dùng SSL"
msgid "Bad mode"
msgstr "Chế độ sai"
-#, fuzzy, c-format
+#, c-format
msgid "Ban on %s by %s, set %s ago"
-msgstr "%s bị %s cấm, đặt %ld giây trước"
+msgstr "%s bị cấm bởi %s, đặt %s trước"
#, c-format
msgid "Ban on %s"
@@ -3541,13 +3533,12 @@ msgstr ""
#. We only want to do the following dance if the connection
#. has not been successfully completed. If it has, just
#. notify the user that their /nick command didn't go.
-#, fuzzy, c-format
+#, c-format
msgid "The nickname \"%s\" is already being used."
-msgstr "Tên chát này đang được dùng"
+msgstr "Tên hiệu « %s » đang được dùng."
-#, fuzzy
msgid "Nickname in use"
-msgstr "Tên hiệu"
+msgstr "Tên hiệu vẫn được dùng"
msgid "Cannot change nick"
msgstr "Không thể thay đổi tên hiệu"
@@ -3591,10 +3582,10 @@ msgstr ""
"trở lại sau khi đi vắng."
msgid "ctcp <nick> <msg>: sends ctcp msg to nick."
-msgstr ""
+msgstr "ctcp <tên_hiệu> <msg>: gửi thông điệp ctcp msg cho tên hiệu đó."
msgid "chanserv: Send a command to chanserv"
-msgstr "chanserv: gửi lệnh cho trình phục vụ kênh"
+msgstr "chanserv: gửi một câu lệnh cho trình phục vụ kênh"
msgid ""
"deop &lt;nick1&gt; [nick2] ...: Remove channel operator status from "
@@ -3788,15 +3779,12 @@ msgstr "Lệnh như thế đã bị lỗi"
msgid "execute"
msgstr "thực hiện"
-#, fuzzy
msgid "Server requires TLS/SSL, but no TLS/SSL support was found."
msgstr ""
-"Máy phục vụ yêu cầu TLS/SSL để đăng nhập. Không tìm thấy khả năng hỗ trợ TLS/"
-"SSL."
+"Máy phục vụ yêu cầu TLS/SSL còn không tìm thấy khả năng hỗ trợ TLS/SSL."
-#, fuzzy
msgid "You require encryption, but no TLS/SSL support was found."
-msgstr "Bạn cần chức năng mật mã, nhưng không tìm thấy hỗ trợ TLS/SSL."
+msgstr "Bạn cần chức năng mật mã còn không tìm thấy hỗ trợ TLS/SSL."
msgid "Server requires plaintext authentication over an unencrypted stream"
msgstr ""
@@ -3813,13 +3801,11 @@ msgstr ""
msgid "Plaintext Authentication"
msgstr "Xác thực nhập thô"
-#, fuzzy
msgid "SASL authentication failed"
-msgstr "Không xác thực được"
+msgstr "Lỗi xác thực SASL"
-#, fuzzy
msgid "Invalid response from server"
-msgstr "Máy phục vụ trả lời không hợp lệ."
+msgstr "Máy phục vụ sai đáp ứng"
msgid "Server does not use any supported authentication method"
msgstr "Máy phục vụ không sử dụng bất kỳ phương thức xác thực được hỗ trợ nào"
@@ -3830,36 +3816,28 @@ msgstr "Bạn cần mật mã, nhưng máy phục vụ này không cung cấp."
msgid "Invalid challenge from server"
msgstr "Kiểm tra từ máy phục vụ không hợp lệ"
-#, fuzzy, c-format
+#, c-format
msgid "SASL error: %s"
-msgstr "Lỗi SASL"
+msgstr "Lỗi SASL: %s"
msgid "The BOSH connection manager terminated your session."
-msgstr ""
+msgstr "Bộ quản lý kết nối BOSH đã chấm dứt buổi hợp của bạn."
-#, fuzzy
msgid "No session ID given"
-msgstr "Không nêu lý do."
+msgstr "Chưa đưa ra mã số buổi hợp"
-#, fuzzy
msgid "Unsupported version of BOSH protocol"
-msgstr "Phiên bản không được hỗ trợ"
+msgstr "Phiên bản giao thức BOSH không được hỗ trợ"
-#, fuzzy
msgid "Unable to establish a connection with the server"
-msgstr ""
-"Không thể thiết lập kết nối đến máy phục vụ :\n"
-"%s"
+msgstr "Không thể thiết lập kết nối đến máy phục vụ"
-#, fuzzy, c-format
+#, c-format
msgid "Unable to establish a connection with the server: %s"
-msgstr ""
-"Không thể thiết lập kết nối đến máy phục vụ :\n"
-"%s"
+msgstr "Không thể thiết lập kết nối đến máy phục vụ : %s"
-#, fuzzy
msgid "Unable to establish SSL connection"
-msgstr "Không thể khởi tạo kết nối"
+msgstr "Không thể thiết lập kết nối SSL"
msgid "Full Name"
msgstr "Tên đầy đủ"
@@ -3930,9 +3908,8 @@ msgstr "Trình khách"
msgid "Operating System"
msgstr "Hệ điều hành"
-#, fuzzy
msgid "Local Time"
-msgstr "Tập tin cục bộ :"
+msgstr "Giờ địa phương"
msgid "Priority"
msgstr "Ưu tiên"
@@ -3942,9 +3919,8 @@ msgstr "Tài nguyên"
#, c-format
msgid "%s ago"
-msgstr ""
+msgstr "%s trước"
-#, fuzzy
msgid "Logged Off"
msgstr "Đã đăng xuất"
@@ -3963,12 +3939,13 @@ msgstr "Ảnh chụp"
msgid "Logo"
msgstr "Biểu hình"
-#, fuzzy, c-format
+#, c-format
msgid ""
"%s will no longer be able to see your status updates. Do you want to "
"continue?"
msgstr ""
-"Bạn sắp gỡ bỏ %s khỏi danh sách bạn bè của bạn. Bạn muốn thực hiện không?"
+"%s thì không còn có khả năng xem lại các bản cập nhật trạng thái của bạn. "
+"Vẫn muốn thực hiện không?"
msgid "Cancel Presence Notification"
msgstr "Thôi thông báo có mặt"
@@ -4121,19 +4098,15 @@ msgstr "Chọn một máy phục vụ hội thảo để hỏi"
msgid "Find Rooms"
msgstr "Tìm phòng"
-#, fuzzy
msgid "Affiliations:"
-msgstr "Bí danh:"
+msgstr "Nhập hội:"
-#, fuzzy
msgid "No users found"
-msgstr "Không tìm thấy người dùng tương ứng"
+msgstr "Không tìm thấy người dùng nào"
-#, fuzzy
msgid "Roles:"
-msgstr "Vị trí"
+msgstr "Vai trò :"
-#, fuzzy
msgid "Ping timed out"
msgstr "Quá hạn ping"
@@ -4141,6 +4114,8 @@ msgid ""
"Unable to find alternative XMPP connection methods after failing to connect "
"directly."
msgstr ""
+"Không tìm thấy phương pháp kết nối XMPP xen kẽ sau khi không kết nối được "
+"một cách trực tiếp."
msgid "Invalid XMPP ID"
msgstr "ID XMPP không hợp lệ"
@@ -4148,9 +4123,8 @@ msgstr "ID XMPP không hợp lệ"
msgid "Invalid XMPP ID. Domain must be set."
msgstr "ID XMPP không hợp lệ. Phải đặt miền (domain)."
-#, fuzzy
msgid "Malformed BOSH URL"
-msgstr "Không kết nối được với máy phục vụ."
+msgstr "Địa chỉ URL BOSH dạng sai"
#, c-format
msgid "Registration of %s@%s successful"
@@ -4239,7 +4213,7 @@ msgid "Re-initializing Stream"
msgstr "Đang khởi tạo lại Stream"
msgid "Server doesn't support blocking"
-msgstr ""
+msgstr "Máy phục vụ không hỗ trợ chức năng chặn"
msgid "Not Authorized"
msgstr "Không cho phép"
@@ -4505,19 +4479,21 @@ msgstr "Không thể đá người dùng %s"
msgid "Unable to ping user %s"
msgstr "Không thể gửi tin hiệu ping cho người dùng %s"
-#, fuzzy, c-format
+#, c-format
msgid "Unable to buzz, because there is nothing known about %s."
-msgstr "Không thể kêu gọi vì không biết gì về người dùng %s."
+msgstr "Không thể kêu gọi vì không biết gì về %s."
-#, fuzzy, c-format
+#, c-format
msgid "Unable to buzz, because %s might be offline."
-msgstr "Không thể kêu gọi, vì người dùng %s có thể chưa đăng nhập."
+msgstr "Không thể kêu gọi, vì %s có thể chưa đăng nhập."
-#, fuzzy, c-format
+#, c-format
msgid ""
"Unable to buzz, because %s does not support it or does not wish to receive "
"buzzes now."
-msgstr "Không thể kêu gọi, vì người dùng %s không hỗ trợ."
+msgstr ""
+"Không thể kêu gọi, vì %s không hỗ trợ hoặc hiện thời không muốn nhận sự kêu "
+"gọi."
#, c-format
msgid "Buzzing %s..."
@@ -4532,35 +4508,36 @@ msgstr "Kêu gọi"
msgid "%s has buzzed you!"
msgstr "%s đã kêu gọi bạn !"
-#, fuzzy, c-format
+#, c-format
msgid "Unable to initiate media with %s: invalid JID"
-msgstr "Không thể gửi tập tin cho %s, JID không hợp lệ"
+msgstr "Không thể khởi đầu phương tiện với %s, JID không hợp lệ"
-#, fuzzy, c-format
+#, c-format
msgid "Unable to initiate media with %s: user is not online"
-msgstr "Không thể gửi tập tin cho %s, người dùng chưa kết nối"
+msgstr "Không thể khởi đầu phương tiện với %s, người dùng chưa kết nối"
-#, fuzzy, c-format
+#, c-format
msgid "Unable to initiate media with %s: not subscribed to user presence"
msgstr ""
-"Không thể gửi tập tin cho %s, không đăng ký với sự có mặt của người dùng"
+"Không thể khởi đầu phương tiện với %s, không đăng ký với sự có mặt của người "
+"dùng"
-#, fuzzy
msgid "Media Initiation Failed"
-msgstr "Lỗi đăng ký"
+msgstr "Lỗi khởi đầu phương tiện"
-#, fuzzy, c-format
+#, c-format
msgid ""
"Please select the resource of %s with which you would like to start a media "
"session."
-msgstr "Hãy chọn tài nguyên nào của %s cho đó bạn muốn gửi tập tin"
+msgstr ""
+"Hãy chọn tài nguyên nào của %s với đó bạn muốn khởi đầu một buổi hợp phương "
+"tiện."
msgid "Select a Resource"
msgstr "Chọn tài nguyên"
-#, fuzzy
msgid "Initiate Media"
-msgstr "Khởi tạo _Chát"
+msgstr "Khởi tạo Phương tiện"
msgid "config: Configure a chat room."
msgstr "config: (viết tắt) cấu hình một phòng trò chuyện."
@@ -4580,30 +4557,33 @@ msgstr "topic [chủ đề mới]: Xem hay thay đổi chủ đề."
msgid "ban &lt;user&gt; [reason]: Ban a user from the room."
msgstr "ban &lt;người_dùng&gt; [lý_do]: Cấm một người dùng ra khỏi phòng."
-#, fuzzy
msgid ""
"affiliate &lt;owner|admin|member|outcast|none&gt; [nick1] [nick2] ...: Get "
"the users with an affiliation or set users' affiliation with the room."
msgstr ""
-"affiliate &lt;người_dùng&gt; &lt;owner|admin|member|outcast|none&gt;: Đặt tư "
-"cách người dùng trong phòng\n"
-" • owner\t\tngười sở hữu\n"
+"affiliate &lt;owner|admin|member|outcast|none&gt; [tên_hiệu1] "
+"[tên_hiệu2] ...\n"
+"Lấy những người dùng nhập hội với phòng đó hoặc đặt sự nhập hội\n"
+"với phòng đó của các người dùng này.\n"
+"\n"
+" • owner\t\tchủ sở hữu\n"
" • admin\t\tquản trị\n"
-" • member\tthành viên\n"
-" • outcast\tngười bị ruồng bỏ\n"
-" • none\t\tkhông có."
+" • member\t\tthành viên\n"
+" • outcast\t\tngười bị ruồng bỏ\n"
+" • none\t\tkhông có"
-#, fuzzy
msgid ""
"role &lt;moderator|participant|visitor|none&gt; [nick1] [nick2] ...: Get the "
"users with a role or set users' role with the room."
msgstr ""
-"role &lt;người_dùng&gt; &lt;moderator|participant|visitor|none&gt;: Đặt "
-"nghiệm vụ của người dùng trong phòng\n"
-" • moderator\t\tđiều tiết viên\n"
+"role &lt;moderator|participant|visitor|none&gt; [tên_hiệu1] [tên_hiệu2] ...\n"
+"Lấy những người dùng có vài trò này, hoặc đặt vai trò này\n"
+"cho các người dùng trong phòng đó.\n"
+"\n"
+" • moderator\t\tđiều hợp viên\n"
" • participant\t\tngười tham gia\n"
" • visitor\t\t\tngười thăm\n"
-" • none\t\t\tkhông có."
+" • none\t\t\tkhông có"
msgid "invite &lt;user&gt; [message]: Invite a user to the room."
msgstr "invite &lt;người_dùng&gt; [thông_điệp]: Mời một người dùng vào phòng."
@@ -4667,13 +4647,12 @@ msgid "File transfer proxies"
msgstr "Ủy nhiệm truyền tập tin"
msgid "BOSH URL"
-msgstr ""
+msgstr "URL BOSH"
#. this should probably be part of global smiley theme settings later on,
#. shared with MSN
-#, fuzzy
msgid "Show Custom Smileys"
-msgstr "Hiển thị hình cười tự chọn"
+msgstr "Hiển thị Hình cười Riêng"
#, c-format
msgid "%s has left the conversation."
@@ -4732,28 +4711,25 @@ msgstr "_Cấu hình phòng"
msgid "_Accept Defaults"
msgstr "Chấp nhận _mặc định"
-#, fuzzy
msgid "No reason"
-msgstr "Không nêu lý do."
+msgstr "Không có lý do"
-#, fuzzy, c-format
+#, c-format
msgid "You have been kicked: (%s)"
-msgstr "Bạn bị %s đá: (%s)"
+msgstr "Bạn bị đá: (%s)"
-#, fuzzy, c-format
+#, c-format
msgid "Kicked (%s)"
-msgstr "Bị %s đá (%s)"
+msgstr "Bị đá (%s)"
-#, fuzzy
msgid "An error occurred on the in-band bytestream transfer\n"
-msgstr "Gặp lỗi trong khi mở tập tin."
+msgstr "Gặp lỗi trong khi truyền luồng byte bên trong băng\n"
-#, fuzzy
msgid "Transfer was closed."
-msgstr "Lỗi truyền tập tin"
+msgstr "Tiến trình truyền bị đóng."
msgid "Failed to open in-band bytestream"
-msgstr ""
+msgstr "Lỗi mở luồng byte bên trong băng"
#, c-format
msgid "Unable to send file to %s, user does not support file transfers"
@@ -4822,11 +4798,10 @@ msgid "Unable to add \"%s\"."
msgstr "Không thể thêm « %s »."
msgid "Buddy Add error"
-msgstr ""
+msgstr "Lỗi thêm bạn chát"
-#, fuzzy
msgid "The username specified does not exist."
-msgstr "Bạn đã ghi rõ một tên người dùng không hợp lệ."
+msgstr "Bạn đã ghi rõ một tên người dùng không tồn tại."
#, c-format
msgid "Buddy list synchronization issue in %s (%s)"
@@ -4838,7 +4813,7 @@ msgid ""
"Do you want this buddy to be added?"
msgstr ""
"%s trên danh sách cục bộ nằm trong nhóm « %s » nhưng không có trong danh sách "
-"máy phục vụ. Bạn có muốn thêm bạn thân này không?"
+"máy phục vụ. Bạn có muốn thêm bạn chát này không?"
#, c-format
msgid ""
@@ -4846,7 +4821,7 @@ msgid ""
"to be added?"
msgstr ""
"%s nằm trên danh sách cục bộ nhưng không nằm trên danh sách máy phục vụ. Bạn "
-"có muốn thêm bạn thân này không?"
+"có muốn thêm bạn chát này không?"
#, c-format
msgid "Unable to parse message"
@@ -5028,9 +5003,8 @@ msgstr "Tập tin bạn bè hỏng"
msgid "Not expected"
msgstr "Bất thường"
-#, fuzzy
msgid "Friendly name is changing too rapidly"
-msgstr "Tên thân thiện thay đổi quá nhanh"
+msgstr "Tên thân thiện cứ thay đổi quá nhanh"
#, c-format
msgid "Server too busy"
@@ -5056,9 +5030,8 @@ msgstr "Passport của trẻ không có sự đồng ý của cha mẹ"
msgid "Passport account not yet verified"
msgstr "Tài khoản Passport chưa được thẩm định"
-#, fuzzy
msgid "Passport account suspended"
-msgstr "Tài khoản Passport chưa được thẩm định"
+msgstr "Tài khoản Passport bị đình chỉ"
#, c-format
msgid "Bad ticket"
@@ -5072,33 +5045,32 @@ msgstr "Mã lỗi không rõ %d"
msgid "MSN Error: %s\n"
msgstr "Lỗi MSN: %s\n"
-#, fuzzy
msgid "Other Contacts"
-msgstr "Liên lạc đã thích"
+msgstr "Liên lạc khác"
-#, fuzzy
msgid "Non-IM Contacts"
-msgstr "Bỏ liên lạc"
+msgstr "Liên lạc khác tin nhắn"
#, c-format
msgid "%s sent a wink. <a href='msn-wink://%s'>Click here to play it</a>"
-msgstr ""
+msgstr "%s đã gửi một wink. <a href='msn-wink://%s'>Bấm đây để phát nó</a>"
#, c-format
msgid "%s sent a wink, but it could not be saved"
-msgstr ""
+msgstr "%s đã gửi một wink mà không lưu được"
#, c-format
msgid "%s sent a voice clip. <a href='audio://%s'>Click here to play it</a>"
msgstr ""
+"%s đã gửi một trích tiếng nói. <a href='audio://%s'>Bấm đây để phát nó</a>"
-#, fuzzy, c-format
+#, c-format
msgid "%s sent a voice clip, but it could not be saved"
-msgstr "%s mời bạn xem máy ảnh Web, mà chưa được hỗ trợ."
+msgstr "%s đã gửi một trích tiếng nói mà không lưu được"
-#, fuzzy, c-format
+#, c-format
msgid "%s sent you a voice chat invite, which is not yet supported."
-msgstr "%s mời bạn xem máy ảnh Web, mà chưa được hỗ trợ."
+msgstr "%s đã gửi cho bạn một lời mời chát tiếng nói mà chưa được hỗ trợ."
msgid "Nudge"
msgstr "Làm nổi bật"
@@ -5149,35 +5121,33 @@ msgstr "Cho phép"
msgid "Disallow"
msgstr "Cấm"
-#, fuzzy, c-format
+#, c-format
msgid "Blocked Text for %s"
-msgstr "Chú thích bạn chát về %s"
+msgstr "Chuỗi bị chặn cho %s"
-#, fuzzy
msgid "No text is blocked for this account."
-msgstr "Dùng biểu tượng bạn chát cho tài khoản này:"
+msgstr "Không có chuỗi bị chặn cho tài khoản này."
#, c-format
msgid ""
"MSN servers are currently blocking the following regular expressions:<br/>%s"
msgstr ""
+"Máy phục vụ MSN hiện thời chặn những biểu thức chính quy theo đây:<br/>%s"
-#, fuzzy
msgid "This account does not have email enabled."
-msgstr "Tài khoản Hotmail này có lẽ chưa kích hoạt."
+msgstr "Tài khoản này chưa hiệu lực thư điện tử."
msgid "Send a mobile message."
-msgstr "Gửi tin nhắn tới di động."
+msgstr "Gửi tin nhẳn tới di động."
msgid "Page"
msgstr "Nhắn tin"
msgid "Playing a game"
-msgstr ""
+msgstr "Chơi lượt"
-#, fuzzy
msgid "Working"
-msgstr "Việc làm"
+msgstr "Đi làm"
msgid "Has you"
msgstr "Có bạn"
@@ -5216,13 +5186,11 @@ msgstr "Nghệ sĩ"
msgid "Album"
msgstr "Tập nhạc"
-#, fuzzy
msgid "Game Title"
-msgstr "Tên điệu"
+msgstr "Tên trò chơi"
-#, fuzzy
msgid "Office Title"
-msgstr "Tên điệu"
+msgstr "Tên chức vụ"
msgid "Set Friendly Name..."
msgstr "Đặt tên thân mật..."
@@ -5243,7 +5211,7 @@ msgid "Allow/Disallow Mobile Pages..."
msgstr "Cho phép/Cấm tin nhắn tới thiết bị di động..."
msgid "View Blocked Text..."
-msgstr ""
+msgstr "Xem chuỗi bị chặn..."
msgid "Open Hotmail Inbox"
msgstr "Mở hộp thư đến Hotmail."
@@ -5257,16 +5225,14 @@ msgstr "Khởi tạo _Chát"
msgid "SSL support is needed for MSN. Please install a supported SSL library."
msgstr "MSN yêu cầu có hỗ trợ SSL. Hãy cài đặt thư viện SSL được hỗ trợ."
-#, fuzzy, c-format
+#, 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 ""
-"Không thể thêm bạn chát %s vì tên người dùng này không hợp lệ. Tên người "
-"dùng phải là một địa chỉ thư điện tử hợp lệ, hoặc bắt đầu bằng một chữ cái "
-"và chỉ chứa chữ cái, chữ số, và khoảng trống, hoặc chỉ chứa chữ số."
+"Không thể thêm bạn chát %s vì tên người dùng này không hợp lệ. Mỗi tên người "
+"dùng cũng phải là một địa chỉ thư điện tử hợp lệ."
-#, fuzzy
msgid "Unable to Add"
msgstr "Không thể thêm"
@@ -5436,9 +5402,8 @@ msgstr ""
"Không tìm thấy thông tin trong lý lịch của người dùng này. Người này rất có "
"thể không tồn tại."
-#, fuzzy
msgid "View web profile"
-msgstr "Ẩn khi ngoại tuyến"
+msgstr "Xem hồ sơ Web"
#. *< type
#. *< ui_requirement
@@ -5467,17 +5432,16 @@ msgstr "nudge: làm nổi bật một liên lạc để thu hút sự chú ý"
msgid "Windows Live ID authentication:Unable to connect"
msgstr "Xác thực ID Windows Live: không thể kết nối"
-#, fuzzy
msgid "Windows Live ID authentication:Invalid response"
-msgstr "Xác thực ID Windows Live: không thể kết nối"
+msgstr "Xác thực ID Windows Live: sai đáp ứng"
#, c-format
msgid "%s just sent you a Nudge!"
msgstr "%s vừa gửi cho bạn một lời kêu gọi."
-#, fuzzy, c-format
+#, c-format
msgid "Unknown error (%d): %s"
-msgstr "Lỗi không rõ (%d)"
+msgstr "Lỗi không rõ (%d): %s"
msgid "Unable to add user"
msgstr "Không thể thêm người dùng"
@@ -5487,13 +5451,11 @@ msgstr "Không thể thêm người dùng"
msgid "Unknown error (%d)"
msgstr "Lỗi không rõ (%d)"
-#, fuzzy
msgid "The following users are missing from your addressbook"
-msgstr "Dưới đây là kết quả tìm kiếm"
+msgstr "Sổ địa chỉ của bạn còn thiếu những người dùng theo đây"
-#, fuzzy
msgid "Mobile message was not sent because it was too long."
-msgstr "Tin nhẳn chưa gửi đi được vì bạn chưa đăng nhập."
+msgstr "Tin nhẳn di động chưa gửi đi được vì quá dài."
#, c-format
msgid ""
@@ -5520,18 +5482,17 @@ msgid ""
"Message was not sent because the system is unavailable. This normally "
"happens when the user is blocked or does not exist."
msgstr ""
+"Tin nhẳn chưa đi được vì hệ thống không sẵn sàng. Trường hợp này bình thường "
+"xảy ra khi người dùng bị chặn hay không tồn tại."
-#, fuzzy
msgid "Message was not sent because messages are being sent too quickly."
-msgstr "Thông điệp không gửi đi được vì gửi quá nhanh:"
+msgstr "Tin nhẳn chưa đi được vì các tin nhẳn được gửi quá nhanh."
-#, fuzzy
msgid "Message was not sent because an unknown encoding error occurred."
-msgstr "Thông điệp không gửi đi được vì có lỗi không rõ :"
+msgstr "Tin nhẳn chưa đi được vì gặp một lỗi mã hoá không được nhận ra."
-#, fuzzy
msgid "Message was not sent because an unknown error occurred."
-msgstr "Thông điệp không gửi đi được vì có lỗi không rõ :"
+msgstr "Tin nhẳn chưa đi được vì gặp một lỗi không được nhận ra."
msgid "Writing error"
msgstr "Lỗi ghi"
@@ -5547,25 +5508,21 @@ msgstr ""
"Lỗi kết nối từ máy phục vụ %s:\n"
"%s"
-#, fuzzy
msgid "Our protocol is not supported by the server"
-msgstr "Giao thức này không được hỗ trợ bởi trình phục vụ."
+msgstr "Giao thức mình không được hỗ trợ bởi trình phục vụ."
-#, fuzzy
msgid "Error parsing HTTP"
-msgstr "Lỗi phân tích HTTP."
+msgstr "Gặp lỗi khi phân tích cú pháp của mã HTTP"
-#, fuzzy
msgid "You have signed on from another location"
-msgstr "Bạn đã đăng nhập từ một vị trí khác."
+msgstr "Bạn đã đăng nhập từ một địa chỉ khác"
msgid "The MSN servers are temporarily unavailable. Please wait and try again."
msgstr ""
"Các máy phục vụ MSN tạm thời không sẵn sàng. Vui lòng chờ và thử lại lần nữa."
-#, fuzzy
msgid "The MSN servers are going down temporarily"
-msgstr "Các máy phục vụ MSN tạm thời bị ngừng."
+msgstr "Các máy phục vụ MSN tạm thời bị ngừng"
#, c-format
msgid "Unable to authenticate: %s"
@@ -5595,13 +5552,13 @@ msgstr "Đang gửi cookie"
msgid "Retrieving buddy list"
msgstr "Đang lấy danh sách bạn bè"
-#, fuzzy, c-format
+#, c-format
msgid "%s requests to view your webcam, but this request is not yet supported."
-msgstr "%s mời bạn xem máy ảnh Web, mà chưa được hỗ trợ."
+msgstr "%s yêu cầu xem máy ảnh Web của bạn, mà chưa được hỗ trợ."
-#, fuzzy, c-format
+#, c-format
msgid "%s invited you to view his/her webcam, but this is not yet supported."
-msgstr "%s mời bạn xem máy ảnh Web, mà chưa được hỗ trợ."
+msgstr "%s mời bạn xem máy ảnh Web của họ, mà chưa được hỗ trợ."
msgid "Away From Computer"
msgstr "Rời khỏi máy tính"
@@ -5641,13 +5598,11 @@ msgstr "Thông điệp không gửi đi được vì có lỗi trong hệ thốn
msgid "Message may have not been sent because an unknown error occurred:"
msgstr "Thông điệp không gửi đi được vì có lỗi không rõ :"
-#, fuzzy
msgid "Delete Buddy from Address Book?"
-msgstr "Thêm vào sổ địa chỉ"
+msgstr "Xoá bạn chát khỏi Sổ địa chỉ ?"
-#, fuzzy
msgid "Do you want to delete this buddy from your address book as well?"
-msgstr "Bạn muốn thêm người này vào danh sách bạn bè của bạn không?"
+msgstr "Bạn cũng muốn xoá bạn chát này khỏi sổ địa chỉ không?"
msgid "The username specified is invalid."
msgstr "Bạn đã ghi rõ một tên người dùng không hợp lệ."
@@ -5660,7 +5615,7 @@ msgid "This Hotmail account may not be active."
msgstr "Tài khoản Hotmail này có lẽ chưa kích hoạt."
msgid "Profile URL"
-msgstr "URL lý lịch"
+msgstr "URL hồ sơ"
#. *< type
#. *< ui_requirement
@@ -5750,11 +5705,8 @@ msgstr ""
"Bạn có muốn đặt một tên người dùng ngay bây giờ không? (Ghi chú : KHÔNG THỂ "
"THAY ĐỔI LẠI !)"
-#, fuzzy
msgid "Lost connection with server"
-msgstr ""
-"Mất kết nối với máy phục vụ\n"
-"%s"
+msgstr "Mất kết nối với máy phục vụ"
#. Can't write _()'d strings in array initializers. Workaround.
#. khc: then use N_() in the array initializer and use _() when they are
@@ -5779,7 +5731,7 @@ msgid "MySpace"
msgstr "MySpace"
msgid "IM Friends"
-msgstr "Bạn bè Nhắn Tin"
+msgstr "Bạn bè Tin nhắn"
#, c-format
msgid ""
@@ -5789,7 +5741,7 @@ msgid_plural ""
"%d buddies were added or updated from the server (including buddies already "
"on the server-side list)"
msgstr[0] ""
-"%d bạn thân đã được thêm hoặc cập nhật từ máy phục vụ (gồm có bạn thân đã "
+"%d bạn chát đã được thêm hoặc cập nhật từ máy phục vụ (gồm có bạn chát đã "
"nằm trên danh sách bên máy phục vụ)"
msgid "Add contacts from server"
@@ -5799,18 +5751,18 @@ msgstr "Thêm liên lạc từ máy phục vụ"
msgid "Protocol error, code %d: %s"
msgstr "Lỗi giao thức, mã %d: %s"
-#, fuzzy, c-format
+#, c-format
msgid ""
"%s Your password is %zu characters, which is longer than the maximum length "
"of %d. Please shorten your password at http://profileedit.myspace.com/index."
"cfm?fuseaction=accountSettings.changePassword and try again."
msgstr ""
-"%s Bạn đã đặt một mật khẩu chứa %d ký tự, vượt quá chiều dài tối đa mong đợi "
-"%d ở MySpaceIM. Hãy cắt ngắn mật khẩu ở địa chỉ « http://profileedit.myspace."
-"com/index.cfm?fuseaction=accountSettings.changePassword », rồi thử lại."
+"%s Bạn đã đặt một mật khẩu chứa %zu ký tự, vượt quá chiều dài tối đa %d. Hãy "
+"cắt ngắn mật khẩu ở địa chỉ « http://profileedit.myspace.com/index.cfm?"
+"fuseaction=accountSettings.changePassword », sau đó thử lại."
msgid "Incorrect username or password"
-msgstr "Tên người dùng hoặc mật khẩu sai"
+msgstr "Tên người dùng hoặc mật khẩu không đúng"
msgid "MySpaceIM Error"
msgstr "Lỗi MySpaceIM"
@@ -5819,19 +5771,19 @@ msgid "Invalid input condition"
msgstr "Điều kiện nhập không hợp lệ"
msgid "Failed to add buddy"
-msgstr "Không thêm được bạn thân"
+msgstr "Không thêm được bạn chát"
msgid "'addbuddy' command failed."
-msgstr "Lỗi « addbuddy » (thêm bạn thân) bị lỗi."
+msgstr "Lỗi « addbuddy » (thêm bạn chát) bị lỗi."
msgid "persist command failed"
msgstr "Lỗi « persist » (bền bỉ) bị lỗi"
msgid "Failed to remove buddy"
-msgstr "Không gỡ bỏ được bạn thân"
+msgstr "Không gỡ bỏ được bạn chát"
msgid "'delbuddy' command failed"
-msgstr "Lỗi « delbuddy » (xoá bạn thân) bị lỗi"
+msgstr "Lỗi « delbuddy » (xoá bạn chát) bị lỗi"
msgid "blocklist command failed"
msgstr "Lỗi « blocklist » (danh sách chận) bị lỗi"
@@ -5907,6 +5859,9 @@ msgid ""
"visit http://editprofile.myspace.com/index.cfm?fuseaction=profile.username "
"to set your username."
msgstr ""
+"Gặp lỗi trong khi thử đặt tên người dùng. Hãy thử lại, hoặc thăm địa chỉ « "
+"http://editprofile.myspace.com/index.cfm?fuseaction=profile.username » để "
+"đặt tên người dùng."
msgid "MySpaceIM - Username Available"
msgstr "MySpaceIM — Tên người dùng sẵn sàng"
@@ -6165,9 +6120,9 @@ msgstr "Người dùng này đang ngoại tuyến hoặc bạn đã bị chặn
msgid "Unknown error: 0x%X"
msgstr "Lỗi không rõ : 0x%X"
-#, fuzzy, c-format
+#, c-format
msgid "Unable to login: %s"
-msgstr "Không thể đăng nhập"
+msgstr "Không thể đăng nhập: %s"
#, c-format
msgid "Unable to send message. Could not get details for user (%s)."
@@ -6299,12 +6254,11 @@ msgid ""
"%s appears to be offline and did not receive the message that you just sent."
msgstr "%s có vẻ là đang ngoại tuyến nên không thể nhận tin bạn vừa gửi."
-#, fuzzy
msgid ""
"Unable to connect to server. Please enter the address of the server to which "
"you wish to connect."
msgstr ""
-"Không thể kết nối đến máy phục vụ. Hãy nhập địa chỉ của máy phục vụ tới đó "
+"Không thể kết nối tới máy phục vụ. Hãy nhập địa chỉ của máy phục vụ tới đó "
"bạn muốn kết nối."
msgid "This conference has been closed. No more messages can be sent."
@@ -6330,9 +6284,9 @@ msgid "Server port"
msgstr "Cổng máy phục vụ"
#. Note to translators: %s in this string is a URL
-#, fuzzy, c-format
+#, c-format
msgid "Received unexpected response from %s"
-msgstr "Nhận được đáp ứng HTTP bất thường từ máy phục vụ."
+msgstr "Nhận được đáp ứng HTTP bất thường từ %s"
#. username connecting too frequently
msgid ""
@@ -6344,12 +6298,12 @@ msgstr ""
#. 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 "Lỗi yêu cầu hiệu bài đăng nhập"
+msgstr "Gặp lỗi khi yêu cầu %s: %s"
msgid "AOL does not allow your screen name to authenticate here"
-msgstr ""
+msgstr "AOL không cho phép tên màn hình xác thực ở đây"
msgid "Could not join chat room"
msgstr "Không thể tham gia phòng trò chuyện"
@@ -6357,9 +6311,8 @@ msgstr "Không thể tham gia phòng trò chuyện"
msgid "Invalid chat room name"
msgstr "Tên phòng trò chuyện không hợp lệ"
-#, fuzzy
msgid "Received invalid data on connection with server"
-msgstr "Nhận dữ liệu không hợp lệ khi kết nối tới máy phục vụ."
+msgstr "Nhận được dữ liệu không hợp lệ khi kết nối tới máy phục vụ"
# AIM là mạng trò chuyện khác.
#. *< type
@@ -6376,7 +6329,7 @@ msgid "AIM Protocol Plugin"
msgstr "Phần bổ sung giao thức AIM"
msgid "ICQ UIN..."
-msgstr ""
+msgstr "ICQ UIN..."
# ICQ là mạng trò chuyện khác.
#. *< type
@@ -6408,7 +6361,6 @@ msgstr "Mất kết nối với người dùng từ xa:<br>%s"
msgid "Received invalid data on connection with remote user."
msgstr "Nhận dữ liệu không hợp lệ khi kết nối với người dùng từ xa."
-#, fuzzy
msgid "Unable to establish a connection with the remote user."
msgstr "Không thể thiết lập kết nối với người dùng từ xa."
@@ -6506,7 +6458,7 @@ msgid ""
"encoding he is using, you can specify it in the advanced account options for "
"your AIM/ICQ account.)"
msgstr ""
-"(Gặp lỗi khi nhận tin này. Bạn thân với họ bạn đang nói chuyện rất có thể sử "
+"(Gặp lỗi khi nhận tin này. Bạn chát với họ bạn đang nói chuyện rất có thể sử "
"dụng bảng mã khác với điều mong đợi. Biết bảng mã đó thì bạn ghi rõ nó trong "
"các tùy chọn tài khoản cấp cao cho tài khoản AIM/ICQ của bạn.)"
@@ -6520,7 +6472,7 @@ msgstr ""
#. Label
msgid "Buddy Icon"
-msgstr "Biểu tượng bạn thân"
+msgstr "Biểu tượng bạn chát"
msgid "Voice"
msgstr "Nói"
@@ -6609,17 +6561,15 @@ msgid "Warning Level"
msgstr "Mức cảnh báo"
msgid "Buddy Comment"
-msgstr "Chú thích bạn thân"
+msgstr "Chú thích bạn chát"
-#, fuzzy, c-format
+#, c-format
msgid "Unable to connect to authentication server: %s"
-msgstr ""
-"Không thể kết nối tới máy phục vụ xác thực:\n"
-"%s"
+msgstr "Không thể kết nối tới máy phục vụ xác thực: %s"
-#, fuzzy, c-format
+#, c-format
msgid "Unable to connect to BOS server: %s"
-msgstr "Không thể kết nối đến máy phục vụ."
+msgstr "Không thể kết nối tới máy phục vụ BOS: %s"
msgid "Username sent"
msgstr "Tên người dùng đã được gửi"
@@ -6631,21 +6581,19 @@ msgstr "Kết nối được thiết lập, đã gửi cookie"
msgid "Finalizing connection"
msgstr "Đang hoàn tất kết nối"
-#, fuzzy, c-format
+#, c-format
msgid ""
"Unable to sign on as %s because the username is invalid. Usernames must be "
"a valid email address, or start with a letter and contain only letters, "
"numbers and spaces, or contain only numbers."
msgstr ""
-"Không thể đăng nhập. Không thể đăng nhập dưới %s vì tên người dùng không hợp "
-"lệ. Tên người dùng phải là một địa chỉ thư điện tử hợp lệ, hoặc bắt đầu với "
-"một chữ cái và chỉ chứa chữ cái, chữ số và khoảng trống, hoặc chỉ chứa chữ "
-"số."
+"Không thể đăng nhập dưới %s vì tên người dùng không hợp lệ. Tên người dùng "
+"phải là một địa chỉ thư điện tử hợp lệ, hoặc bắt đầu với một chữ cái và chỉ "
+"chứa chữ cái, chữ số và khoảng trống, hoặc chỉ chứa chữ số."
-#, fuzzy, c-format
+#, c-format
msgid "You may be disconnected shortly. If so, check %s for updates."
-msgstr ""
-"Bạn có thể bị ngắt kết nối một thời gian ngắn. Hãy kiểm tra %s để cập nhật."
+msgstr "Bạn có thể sắp bị ngắt kết nối. Có thì hãy kiểm tra %s để cập nhật."
msgid "Unable to get a valid AIM login hash."
msgstr "Không thể lấy mã đăng nhập AIM hợp lệ."
@@ -6659,14 +6607,12 @@ msgstr "Nhận được sự cho phép"
#. Unregistered username
#. uid is not exist
#. the username does not exist
-#, fuzzy
msgid "Username does not exist"
-msgstr "Người dùng đó không tồn tại."
+msgstr "Tên người dùng không tồn tại"
#. Suspended account
-#, fuzzy
msgid "Your account is currently suspended"
-msgstr "Tài khoản của bạn tạm thời bị đình chỉ."
+msgstr "Tài khoản của bạn hiện thời bị đình chỉ"
#. service temporarily unavailable
msgid "The AOL Instant Messenger service is temporarily unavailable."
@@ -6678,18 +6624,16 @@ msgid "The client version you are using is too old. Please upgrade at %s"
msgstr "Bạn đang dùng phiên bản trình khách quá cũ. Hãy nâng cấp tại %s"
#. IP address connecting too frequently
-#, fuzzy
msgid ""
"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."
msgstr ""
-"Bạn đã liên tục kết nối và ngắt kết nối quá nhiều. Xin chờ 10 phút rồi thử "
-"lại. Nếu bạn tiếp tục kết nối, bạn sẽ phải đợi lâu hơn."
+"Bạn đã liên tục kết nối và ngắt kết nối quá nhiều. Xin chờ một phút rồi thử "
+"lại. Cứ thử kết nối thì bạn phải đợi ngay cả lâu hơn."
# SecurID là tên: đừng dịch
-#, fuzzy
msgid "The SecurID key entered is invalid"
-msgstr "Khoá SecurID nhập vào không hợp lệ."
+msgstr "Khoá SecurID nhập vào không hợp lệ"
msgid "Enter SecurID"
msgstr "Nhập vào SecurID"
@@ -6768,7 +6712,7 @@ msgstr ""
#, c-format
msgid "ICQ user %u has sent you a buddy: %s (%s)"
-msgstr "Người dùng ICQ %u gửi tới bạn một bạn thân: %s (%s)"
+msgstr "Người dùng ICQ %u gửi tới bạn một bạn chát: %s (%s)"
msgid "Do you want to add this buddy to your buddy list?"
msgstr "Bạn muốn thêm người này vào danh sách bạn bè của bạn không?"
@@ -7009,19 +6953,18 @@ msgstr[0] ""
msgid "Away message too long."
msgstr "Thông báo vắng mặt quá dài."
-#, fuzzy, c-format
+#, c-format
msgid ""
"Unable to add the buddy %s because the username is invalid. Usernames must "
"be a valid email address, or start with a letter and contain only letters, "
"numbers and spaces, or contain only numbers."
msgstr ""
-"Không thể thêm bạn chát %s vì tên người dùng này không hợp lệ. Tên người "
+"Không thể thêm bạn chát %s vì tên người dùng này không hợp lệ. Mỗi tên người "
"dùng phải là một địa chỉ thư điện tử hợp lệ, hoặc bắt đầu bằng một chữ cái "
"và chỉ chứa chữ cái, chữ số, và khoảng trống, hoặc chỉ chứa chữ số."
-#, fuzzy
msgid "Unable to Retrieve Buddy List"
-msgstr "Không thể nhận danh sách bạn bè"
+msgstr "Không thể lấy Danh sách Bạn bè"
msgid ""
"The AIM servers were temporarily unable to send your buddy list. Your buddy "
@@ -7033,7 +6976,7 @@ msgstr ""
msgid "Orphans"
msgstr "Thừa"
-#, fuzzy, c-format
+#, c-format
msgid ""
"Unable to add the buddy %s because you have too many buddies in your buddy "
"list. Please remove one and try again."
@@ -7044,7 +6987,7 @@ msgstr ""
msgid "(no name)"
msgstr "(không tên)"
-#, fuzzy, c-format
+#, c-format
msgid "Unable to add the buddy %s for an unknown reason."
msgstr "Không thể thêm bạn chát %s vì lý do không rõ."
@@ -7113,9 +7056,8 @@ msgstr ""
msgid "C_onnect"
msgstr "_Kết nối"
-#, fuzzy
msgid "You closed the connection."
-msgstr "Máy phục vụ đã đóng kết nối."
+msgstr "Bạn đã đóng kết nối."
msgid "Get AIM Info"
msgstr "Lấy thông tin AIM"
@@ -7127,12 +7069,11 @@ msgstr "Sửa chú thích bạn chát"
msgid "Get Status Msg"
msgstr "Lấy thông điệp trạng thái"
-#, fuzzy
msgid "End Direct IM Session"
-msgstr "Đã thiết lập nhắn tin nhanh trực tiếp"
+msgstr "Kết thúc buổi hợp Tin nhắn Trực tiếp"
msgid "Direct IM"
-msgstr "Nhắn Tin Trực Tiếp"
+msgstr "Tin nhắn Trực tiếp"
msgid "Re-request Authorization"
msgstr "Yêu cầu lại sự cho phép"
@@ -7213,9 +7154,8 @@ msgstr "Tìm kiếm bạn chát theo địa chỉ thư..."
msgid "Search for Buddy by Information"
msgstr "Tìm kiếm bạn chát theo thông tin"
-#, fuzzy
msgid "Use clientLogin"
-msgstr "Người dùng chưa đăng nhập"
+msgstr "Dùng clientLogin"
msgid ""
"Always use AIM/ICQ proxy server for\n"
@@ -7329,16 +7269,14 @@ msgstr "Hợi"
msgid "Other"
msgstr "Khác"
-#, fuzzy
msgid "Visible"
-msgstr "Giấu mặt"
+msgstr "Hiện rõ"
msgid "Friend Only"
-msgstr ""
+msgstr "Chỉ người bạn"
-#, fuzzy
msgid "Private"
-msgstr "Riêng tư"
+msgstr "Riêng"
msgid "QQ Number"
msgstr "Số QQ"
@@ -7355,9 +7293,8 @@ msgstr "Mã bưu điện"
msgid "Phone Number"
msgstr "Điện thoại"
-#, fuzzy
msgid "Authorize adding"
-msgstr "Cho phép bạn thân không?"
+msgstr "Cho phép thêm"
msgid "Cellphone Number"
msgstr "Điện thoại di động"
@@ -7365,64 +7302,50 @@ msgstr "Điện thoại di động"
msgid "Personal Introduction"
msgstr "Giới thiệu cá nhân"
-#, fuzzy
msgid "City/Area"
-msgstr "T.P."
+msgstr "T.P./Vùng"
-#, fuzzy
msgid "Publish Mobile"
-msgstr "Điện thoại di động cá nhân"
+msgstr "Xuất số di động"
-#, fuzzy
msgid "Publish Contact"
-msgstr "Liên lạc bí danh"
+msgstr "Xuất liên lạc"
msgid "College"
msgstr "Cao đẳng"
-#, fuzzy
msgid "Horoscope"
-msgstr "Ký hiệu tử vi"
+msgstr "Tử vi"
-#, fuzzy
msgid "Zodiac"
-msgstr "Ký hiệu hoàng đạo"
+msgstr "Hoàng đạo"
-#, fuzzy
msgid "Blood"
-msgstr "Bị chặn"
+msgstr "Máu"
-#, fuzzy
msgid "True"
-msgstr "Kim Ngưu"
+msgstr "Đúng"
-#, fuzzy
msgid "False"
-msgstr "Bị lỗi"
+msgstr "Sai"
-#, fuzzy
msgid "Modify Contact"
-msgstr "Sửa tài khoản"
+msgstr "Sửa liên lạc"
-#, fuzzy
msgid "Modify Address"
-msgstr "Địa chỉ nhà"
+msgstr "Sửa địa chỉ"
-#, fuzzy
msgid "Modify Extended Information"
-msgstr "Sửa đổi thông tin của tôi"
+msgstr "Sửa thông tin mở rộng"
-#, fuzzy
msgid "Modify Information"
-msgstr "Sửa đổi thông tin của tôi"
+msgstr "Sửa thông tin"
-#, fuzzy
msgid "Update"
-msgstr "Cập nhật lần cuối"
+msgstr "Cập nhật"
-#, fuzzy
msgid "Could not change buddy information."
-msgstr "Hãy nhập thông tin về bạn thân."
+msgstr "Không thể sửa đổi thông tin về bạn chát."
msgid "Mobile"
msgstr "Di động"
@@ -7431,99 +7354,84 @@ msgid "Note"
msgstr "Ghi chú"
#. callback
-#, fuzzy
msgid "Buddy Memo"
-msgstr "Biểu tượng bạn thân"
+msgstr "Ghi nhớ bạn chát"
msgid "Change his/her memo as you like"
-msgstr ""
+msgstr "Sửa đổi bản ghi nhớ của họ theo ý kiến của bạn"
-#, fuzzy
msgid "_Modify"
-msgstr "Sửa"
+msgstr "_Sửa"
-#, fuzzy
msgid "Memo Modify"
-msgstr "Sửa"
+msgstr "Sửa ghi nhớ"
-#, fuzzy
msgid "Server says:"
-msgstr "Máy phục vụ bận"
+msgstr "Máy phục vụ nói:"
msgid "Your request was accepted."
-msgstr ""
+msgstr "Yêu cầu của bạn đã được chấp nhận."
msgid "Your request was rejected."
-msgstr ""
+msgstr "Yêu cầu của bạn bị từ chối."
-#, fuzzy, c-format
+#, c-format
msgid "%u requires verification"
-msgstr "Cần thiết sự cho phép"
+msgstr "%u cần thiết sự thẩm tra"
-#, fuzzy
msgid "Add buddy question"
-msgstr "Có thêm bạn thân vào danh sách của bạn không?"
+msgstr "Hỏi câu cho bạn chát"
-#, fuzzy
msgid "Enter answer here"
-msgstr "Gõ yêu cầu vào đây"
+msgstr "Gõ đáp ứng vào đây"
msgid "Send"
msgstr "Gửi"
-#, fuzzy
msgid "Invalid answer."
-msgstr "Tên người dùng sai."
+msgstr "Sai đáp ứng."
msgid "Authorization denied message:"
msgstr "Thông điệp từ chối cho phép:"
-#, fuzzy
msgid "Sorry, you're not my style."
-msgstr "Tiếc là tôi quá bận..."
+msgstr "Tiếc là tôi quá bận."
-#, fuzzy, c-format
+#, c-format
msgid "%u needs authorization"
-msgstr "Người dùng %d yêu cầu sự cho phép"
+msgstr "%u yêu cầu sự cho phép"
-#, fuzzy
msgid "Add buddy authorize"
-msgstr "Có thêm bạn thân vào danh sách của bạn không?"
+msgstr "Thêm sự cho phép bạn chát"
-#, fuzzy
msgid "Enter request here"
msgstr "Gõ yêu cầu vào đây"
msgid "Would you be my friend?"
msgstr "Bạn có muốn nói chuyện phải không?"
-#, fuzzy
msgid "QQ Buddy"
-msgstr "Bạn chát"
+msgstr "Bạn chát QQ"
-#, fuzzy
msgid "Add buddy"
-msgstr "Thêm bạn thân"
+msgstr "Thêm bạn chát"
-#, fuzzy
msgid "Invalid QQ Number"
-msgstr "Mặt QQ không hợp lệ"
+msgstr "Con số QQ không hợp lệ"
-#, fuzzy
msgid "Failed sending authorize"
-msgstr "Xin hãy cho phép tôi."
+msgstr "Lỗi gửi sự cho phép"
-#, fuzzy, c-format
+#, c-format
msgid "Failed removing buddy %u"
-msgstr "Không gỡ bỏ được bạn thân"
+msgstr "Lỗi gỡ bỏ bạn chát %u"
-#, fuzzy, c-format
+#, c-format
msgid "Failed removing me from %d's buddy list"
-msgstr "%s đã loại bỏ bạn ra khỏi danh sách bạn bè."
+msgstr "Lỗi gỡ bỏ mình khỏi danh sách bạn chát của %d"
-#, fuzzy
msgid "No reason given"
-msgstr "Không nêu lý do."
+msgstr "Không nêu lý do"
#. only need to get value
#, c-format
@@ -7533,9 +7441,9 @@ msgstr "Bạn đã được %s thêm"
msgid "Would you like to add him?"
msgstr "Bạn có muốn thêm họ không?"
-#, fuzzy, c-format
+#, c-format
msgid "Rejected by %s"
-msgstr "Từ chối"
+msgstr "Bị %s từ chối"
#, c-format
msgid "Message: %s"
@@ -7550,90 +7458,73 @@ msgstr "ID nhóm"
msgid "QQ Qun"
msgstr "QQ Qun"
-#, fuzzy
msgid "Please enter Qun number"
-msgstr "Hãy nhập tên mới cho %s"
+msgstr "Hãy gõ con số Qun"
-#, fuzzy
msgid "You can only search for permanent Qun\n"
-msgstr "Bạn chỉ có khả năng tìm kiếm nhóm QQ bên bỉ\n"
+msgstr "Bạn chỉ có khả năng tìm kiếm QQ bên bỉ\n"
-#, fuzzy
msgid "(Invalid UTF-8 string)"
-msgstr "Thiết lập ủy nhiệm không hợp lệ"
+msgstr "(Chuỗi UTF-8 không hợp lệ)"
-#, fuzzy
msgid "Not member"
-msgstr "Tôi không phải là thành viên"
+msgstr "Không phải thành viên"
-#, fuzzy
msgid "Member"
-msgstr "Là thành viên từ"
+msgstr "Thành viên"
-#, fuzzy
msgid "Requesting"
-msgstr "Hộp thoại yêu cầu"
+msgstr " Đăng yêu cầu"
-# Tên của ứng dụng khách tin nhắn khác: đừng dịch.
-#, fuzzy
msgid "Admin"
-msgstr "Adium"
+msgstr "Quản trị"
-#, fuzzy
msgid "Notice"
-msgstr "Ghi chú"
+msgstr "Thông báo"
-#, fuzzy
msgid "Detail"
-msgstr "Mặc định"
+msgstr "Chi tiết"
msgid "Creator"
msgstr "Người tạo"
-#, fuzzy
msgid "About me"
-msgstr "Giới thiệu %s"
+msgstr "Giới thiệu mình"
-#, fuzzy
msgid "Category"
-msgstr "Lỗi chát"
+msgstr "Loại"
-#, fuzzy
msgid "The Qun does not allow others to join"
-msgstr "Nhóm này không cho phép người khác tham gia"
+msgstr "Qun này không cho phép người khác tham gia"
-#, fuzzy
msgid "Join QQ Qun"
-msgstr "Tham gia chát"
+msgstr "Tham gia QQ Qun"
msgid "Input request here"
msgstr "Gõ yêu cầu vào đây"
-#, fuzzy, c-format
+#, c-format
msgid "Successfully joined Qun %s (%u)"
-msgstr "Bạn đã sửa đổi thành công thành viên Qun"
+msgstr "Đã tham gia thành công Qun %s (%u)"
-#, fuzzy
msgid "Successfully joined Qun"
-msgstr "Bạn đã sửa đổi thành công thành viên Qun"
+msgstr "Đã tham gia thành công Qun"
#, c-format
msgid "Qun %u denied from joining"
-msgstr ""
+msgstr "Qun %u từ chối tham gia"
msgid "QQ Qun Operation"
msgstr "Thao tác QQ Qun"
-#, fuzzy
msgid "Failed:"
-msgstr "Bị lỗi"
+msgstr "Bị lỗi:"
msgid "Join Qun, Unknown Reply"
-msgstr ""
+msgstr "Tham gia Qun, Đáp ứng không rõ"
-#, fuzzy
msgid "Quit Qun"
-msgstr "QQ Qun"
+msgstr "Thoát khỏi Qun"
msgid ""
"Note, if you are the creator, \n"
@@ -7642,51 +7533,47 @@ msgstr ""
"Ghi chú : nếu bạn là người tạo, \n"
"cuối cùng thao tác này sẽ gỡ bỏ Qun này."
-#, fuzzy
msgid "Sorry, you are not our style"
-msgstr "Tiếc là tôi quá bận..."
+msgstr "Tiếc là nhóm này quá đầy"
-#, fuzzy
msgid "Successfully changed Qun members"
-msgstr "Bạn đã sửa đổi thành công thành viên Qun"
+msgstr "Đã thay đổi thành công thành viên Qun"
-#, fuzzy
msgid "Successfully changed Qun information"
-msgstr "Bạn đã sửa đổi thành công thông tin Qun"
+msgstr "Đã sửa đổi thành công thông tin Qun"
msgid "You have successfully created a Qun"
msgstr "Bạn đã tạo thành công một Qun"
-#, fuzzy
msgid "Would you like to set up detailed information now?"
-msgstr "Bạn có muốn thiết lập chi tiết Qun ngay bây giờ không?"
+msgstr "Bạn có muốn thiết lập thông tin chi tiết ngay bây giờ không?"
msgid "Setup"
msgstr "Thiết lập"
-#, fuzzy, c-format
+#, c-format
msgid "%u requested to join Qun %u for %s"
-msgstr "Người dùng %d đã yêu cầu tham gia nhóm %d"
+msgstr "%u đã yêu cầu tham gia Qun %u cho %s"
-#, fuzzy, c-format
+#, c-format
msgid "%u request to join Qun %u"
-msgstr "Người dùng %d đã yêu cầu tham gia nhóm %d"
+msgstr "%u yêu cầu tham gia Qun %u"
-#, fuzzy, c-format
+#, c-format
msgid "Failed to join Qun %u, operated by admin %u"
-msgstr "Không tham gia được với bạn chát"
+msgstr "Lỗi tham gia Qun %u, được %u quản trị"
#, c-format
msgid "<b>Joining Qun %u is approved by admin %u for %s</b>"
-msgstr ""
+msgstr "<b>Tham gia Qun %u được %u tán thành cho %s</b>"
-#, fuzzy, c-format
+#, c-format
msgid "<b>Removed buddy %u.</b>"
-msgstr "Bỏ bạn chát"
+msgstr "<b>Đã gỡ bỏ bạn chát %u.</b>"
-#, fuzzy, c-format
+#, c-format
msgid "<b>New buddy %u joined.</b>"
-msgstr "Bỏ bạn chát"
+msgstr "<b>Bạn chát mới %u đã tham gia.</b>"
#, c-format
msgid "Unknown-%d"
@@ -7696,151 +7583,139 @@ msgid "Level"
msgstr "Cấp"
msgid " VIP"
-msgstr ""
+msgstr " VIP"
msgid " TCP"
-msgstr ""
+msgstr " TCP"
-#, fuzzy
msgid " FromMobile"
-msgstr "Di động"
+msgstr " FromMobile"
-#, fuzzy
msgid " BindMobile"
-msgstr "Di động"
+msgstr " BindMobile"
-#, fuzzy
msgid " Video"
-msgstr "Ảnh động trực tiếp"
+msgstr " Phim"
-#, fuzzy
msgid " Zone"
-msgstr "Không"
+msgstr " Vùng"
msgid "Flag"
-msgstr ""
+msgstr "Cờ"
msgid "Ver"
-msgstr ""
+msgstr "Pb"
msgid "Invalid name"
msgstr "Tên không hợp lệ"
-#, fuzzy
msgid "Select icon..."
-msgstr "Chọn thư mục..."
+msgstr "Chọn biểu tượng..."
-#, fuzzy, c-format
+#, c-format
msgid "<b>Login time</b>: %d-%d-%d, %d:%d:%d<br>\n"
-msgstr "<b>Thời gian đăng nhập:</b> %s<br>\n"
+msgstr "<b>Thời gian đăng nhập</b>: %d-%d-%d, %d:%d:%d<br>\n"
-#, fuzzy, c-format
+#, c-format
msgid "<b>Total Online Buddies</b>: %d<br>\n"
-msgstr "<b>Hiện thời trực tuyến</b>: %d<br>\n"
+msgstr "<b>Tổng các bạn chát trực tuyến</b>: %d<br>\n"
-#, fuzzy, c-format
+#, c-format
msgid "<b>Last Refresh</b>: %d-%d-%d, %d:%d:%d<br>\n"
-msgstr "<b>Cập nhật cuối:</b> %s<br>\n"
+msgstr "<b>Cập nhật cuối:</b>: %d-%d-%d, %d:%d:%d<br>\n"
-#, fuzzy, c-format
+#, c-format
msgid "<b>Server</b>: %s<br>\n"
-msgstr "<b>IP máy phục vụ </b>: %s: %d<br>\n"
+msgstr "<b>Máy phục vụ</b>: %s<br>\n"
-#, fuzzy, c-format
+#, c-format
msgid "<b>Client Tag</b>: %s<br>\n"
-msgstr "<b>Thời gian đăng nhập:</b> %s<br>\n"
+msgstr "<b>Thẻ ứng dụng khách</b>: %s<br>\n"
#, c-format
msgid "<b>Connection Mode</b>: %s<br>\n"
-msgstr "<b>Chế độ kết nối:</b> %s<br>\n"
+msgstr "<b>Chế độ kết nối</b>: %s<br>\n"
-#, fuzzy, c-format
+#, c-format
msgid "<b>My Internet IP</b>: %s:%d<br>\n"
-msgstr "<b>Chế độ kết nối:</b> %s<br>\n"
+msgstr "<b>Địa chỉ IP Internet của mình</b>: %s:%d<br>\n"
-#, fuzzy, c-format
+#, c-format
msgid "<b>Sent</b>: %lu<br>\n"
-msgstr "<b>IP máy phục vụ </b>: %s: %d<br>\n"
+msgstr "<b>Đã gửi</b>: %lu<br>\n"
-#, fuzzy, c-format
+#, c-format
msgid "<b>Resend</b>: %lu<br>\n"
-msgstr "<b>Cập nhật cuối:</b> %s<br>\n"
+msgstr "<b>Gửi lại</b>: %lu<br>\n"
-#, fuzzy, c-format
+#, c-format
msgid "<b>Lost</b>: %lu<br>\n"
-msgstr "<b>Cập nhật cuối:</b> %s<br>\n"
+msgstr "<b>Bị mất</b>: %lu<br>\n"
-#, fuzzy, c-format
+#, c-format
msgid "<b>Received</b>: %lu<br>\n"
-msgstr "<b>IP máy phục vụ </b>: %s: %d<br>\n"
+msgstr "<b>Đã nhận</b>: %lu<br>\n"
-#, fuzzy, c-format
+#, c-format
msgid "<b>Received Duplicate</b>: %lu<br>\n"
-msgstr "<b>IP công của tôi:</b> %s<br>\n"
+msgstr "<b>Đã nhận bản sao</b>: %lu<br>\n"
-#, fuzzy, c-format
+#, c-format
msgid "<b>Time</b>: %d-%d-%d, %d:%d:%d<br>\n"
-msgstr "<b>Thời gian đăng nhập:</b> %s<br>\n"
+msgstr "<b>Thời gian</b>: %d-%d-%d, %d:%d:%d<br>\n"
-#, fuzzy, c-format
+#, c-format
msgid "<b>IP</b>: %s<br>\n"
-msgstr "<b>IP máy phục vụ </b>: %s: %d<br>\n"
+msgstr "<b>IP</b>: %s<br>\n"
msgid "Login Information"
msgstr "Thông tin đăng nhập"
msgid "<p><b>Original Author</b>:<br>\n"
-msgstr ""
+msgstr "<p><b>Tác giả gốc</b>:<br>\n"
msgid "<p><b>Code Contributors</b>:<br>\n"
-msgstr ""
+msgstr "<p><b>Người đóng góp mã nguồn</b>:<br>\n"
-#, fuzzy
msgid "<p><b>Lovely Patch Writers</b>:<br>\n"
-msgstr "<b>Cập nhật cuối:</b> %s<br>\n"
+msgstr "<p><b>Người tạo đắp vá rất hữu ích</b>:<br>\n"
-#, fuzzy
msgid "<p><b>Acknowledgement</b>:<br>\n"
-msgstr "<b>IP máy phục vụ </b>: %s: %d<br>\n"
+msgstr "<p><b>Công trạng</b>:<br>\n"
-#, fuzzy
msgid "<p><b>Scrupulous Testers</b>:<br>\n"
-msgstr "<b>Cập nhật cuối:</b> %s<br>\n"
+msgstr "<p><b>Người thữ kỹ</b>:<br>\n"
msgid "and more, please let me know... thank you!))"
-msgstr ""
+msgstr "và các người khác (xin gửi chi tiết))"
msgid "<p><i>And, all the boys in the backroom...</i><br>\n"
-msgstr ""
+msgstr "<p><i>Và tất cả các người nghiên cứu bí mật...</i><br>\n"
msgid "<i>Feel free to join us!</i> :)"
-msgstr ""
+msgstr "<i>Mời bạn tham gia !</i> :)"
-#, fuzzy, c-format
+#, c-format
msgid "About OpenQ %s"
-msgstr "Giới thiệu %s"
+msgstr "Giới thiệu OpenQ %s"
-#, fuzzy
msgid "Change Icon"
-msgstr "Lưu biểu tượng"
+msgstr "Đổi biểu tượng"
msgid "Change Password"
msgstr "Đổi mật khẩu"
-#, fuzzy
msgid "Account Information"
-msgstr "Thông tin đăng nhập"
+msgstr "Thông tin Tài khoản"
msgid "Update all QQ Quns"
-msgstr ""
+msgstr "Cập nhật mọi QQ Qun"
-#, fuzzy
msgid "About OpenQ"
-msgstr "Giới thiệu %s"
+msgstr "Giới thiệu OpenQ"
-#, fuzzy
msgid "Modify Buddy Memo"
-msgstr "Địa chỉ nhà"
+msgstr "Sửa ghi nhớ bạn chát"
#. *< type
#. *< ui_requirement
@@ -7852,189 +7727,171 @@ msgstr "Địa chỉ nhà"
#. *< version
#. * summary
#. * description
-#, fuzzy
msgid "QQ Protocol Plugin"
-msgstr "Phần bổ sung\tgiao thức QQ"
+msgstr "Phần bổ sung giao thức QQ"
-#, fuzzy
msgid "Auto"
-msgstr "Tác giả"
+msgstr "Tự động"
-#, fuzzy
msgid "Select Server"
-msgstr "Chọn người dùng"
+msgstr "Chọn máy phục vụ"
msgid "QQ2005"
-msgstr ""
+msgstr "QQ2005"
msgid "QQ2007"
-msgstr ""
+msgstr "QQ2007"
msgid "QQ2008"
-msgstr ""
+msgstr "QQ2008"
-#, fuzzy
msgid "Connect by TCP"
-msgstr "Kết nối bằng TCP"
+msgstr "Kết nối qua TCP"
-#, fuzzy
msgid "Show server notice"
-msgstr "Cổng máy phục vụ"
+msgstr "Hiện thông báo máy phục vụ"
-#, fuzzy
msgid "Show server news"
-msgstr "Địa chỉ máy phục vụ"
+msgstr "Hiện tin tức máy phục vụ"
msgid "Show chat room when msg comes"
-msgstr ""
+msgstr "Nhận được tin nhẳn thì cũng hiển thị phòng chát"
-#, fuzzy
msgid "Keep alive interval (seconds)"
-msgstr "Lỗi giữ cho kết nối hoạt động"
+msgstr "Khoảng giữ cho kết nối hoạt động (giây)"
-#, fuzzy
msgid "Update interval (seconds)"
-msgstr "Lỗi giữ cho kết nối hoạt động"
+msgstr "Khoảng cập nhật (giây)"
-#, fuzzy
msgid "Unable to decrypt server reply"
-msgstr "Không thể lấy thông tin về máy phục vụ"
+msgstr "Không thể giải mật mã đáp ứng máy phục vụ"
#, c-format
msgid "Failed requesting token, 0x%02X"
-msgstr ""
+msgstr "Lỗi yêu cầu hiệu bài, 0x%02X"
-#, fuzzy, c-format
+#, c-format
msgid "Invalid token len, %d"
-msgstr "Tựa đề không hợp lệ"
+msgstr "Chiều dài hiệu bài không hợp lệ, %d"
#. extend redirect used in QQ2006
msgid "Redirect_EX is not currently supported"
-msgstr ""
+msgstr "Redirect_EX hiện thời không được hỗ trợ"
#. need activation
#. need activation
#. need activation
-#, fuzzy
msgid "Activation required"
-msgstr "Yêu cầu đăng ký"
+msgstr "Yêu cầu kích hoạt"
#, c-format
msgid "Unknown reply code when logging in (0x%02X)"
-msgstr ""
+msgstr "Gặp mã đáp ứng không được nhận ra khi đăng nhập (0x%02X)"
-#, fuzzy
msgid "Requesting captcha"
-msgstr "Đang yêu cầu sự chú ý của %s..."
+msgstr "Đang yêu cầu Captcha"
-#, fuzzy
msgid "Checking captcha"
-msgstr "Đang yêu cầu sự chú ý của %s..."
+msgstr "Đang kiểm tra Captcha"
-#, fuzzy
msgid "Failed captcha verification"
-msgstr "Lỗi xác thực Yahoo"
+msgstr "Lỗi thẩm tra Captcha"
-#, fuzzy
msgid "Captcha Image"
-msgstr "Lưu ảnh"
+msgstr "Ảnh Captcha"
-#, fuzzy
msgid "Enter code"
-msgstr "Nhập mật khẩu"
+msgstr "Gõ mã"
-#, fuzzy
msgid "QQ Captcha Verification"
-msgstr "Thẩm tra chứng nhận SSL"
+msgstr "Thẩm tra Captcha QQ"
-#, fuzzy
msgid "Enter the text from the image"
-msgstr "Hãy nhập tên của nhóm"
+msgstr "Hãy nhập chuỗi từ ảnh"
#, c-format
msgid "Unknown reply when checking password (0x%02X)"
-msgstr ""
+msgstr "Không nhận ra đáp ứng khi kiểm tra mật khẩu (0x%02X)"
#, c-format
msgid ""
"Unknown reply code when logging in (0x%02X):\n"
"%s"
msgstr ""
+"Gặp mã đáp ứng không được nhận ra khi đăng nhập vào (0x%02X):\n"
+"%s"
msgid "Socket error"
msgstr "Lỗi ổ cắm"
-#, fuzzy
msgid "Getting server"
-msgstr "Lập thông tin người dùng..."
+msgstr "Đang lấy máy phục vụ"
-#, fuzzy
msgid "Requesting token"
-msgstr "Yêu cầu bị từ chối"
+msgstr "Đang yêu cầu hiệu bài"
-#, fuzzy
msgid "Unable to resolve hostname"
-msgstr "Không thể kết nối đến máy phục vụ."
+msgstr "Không thể quyết định tên máy"
-#, fuzzy
msgid "Invalid server or port"
-msgstr "Lỗi không hợp lệ"
+msgstr "Sai máy phục vụ hay cổng"
-#, fuzzy
msgid "Connecting to server"
-msgstr "Đang kết nối tới máy phục vụ SILC"
+msgstr "Đang kết nối tới máy phục vụ"
-#, fuzzy
msgid "QQ Error"
-msgstr "Lỗi QQid"
+msgstr "Lỗi QQ"
-#, fuzzy, c-format
+#, c-format
msgid ""
"Server News:\n"
"%s\n"
"%s\n"
"%s"
-msgstr "Chuyển tiếp máy phục vụ ICQ"
+msgstr ""
+"Tin tức Máy phục vụ :\n"
+"%s\n"
+"%s\n"
+"%s"
-#, fuzzy, c-format
+#, c-format
msgid "%s:%s"
-msgstr "%s (%s)"
+msgstr "%s:%s"
-#, fuzzy, c-format
+#, c-format
msgid "From %s:"
-msgstr "Từ"
+msgstr "Từ %s:"
-#, fuzzy, c-format
+#, c-format
msgid ""
"Server notice From %s: \n"
"%s"
-msgstr "Hướng dẫn máy phục vụ : %s"
+msgstr ""
+"Thông báo máy phục vụ Từ %s: \n"
+"%s"
-#, fuzzy
msgid "Unknown SERVER CMD"
-msgstr "Lý do không rõ"
+msgstr "Câu lệnh máy phục vụ (SERVER CMD) không rõ"
#, c-format
msgid ""
"Error reply of %s(0x%02X)\n"
"Room %u, reply 0x%02X"
msgstr ""
+"Lỗi đáp ứng %s(0x%02X)\n"
+"Phòng %u, đáp ứng 0x%02X"
-#, fuzzy
msgid "QQ Qun Command"
-msgstr "Lệnh"
+msgstr "Lệnh QQ Qun"
-#, fuzzy
msgid "Unable to decrypt login reply"
-msgstr "Không thể lấy thông tin về máy phục vụ"
+msgstr "Không thể giải mật mã đáp ứng đăng nhập"
-#, fuzzy
msgid "Unknown LOGIN CMD"
-msgstr "Lý do không rõ"
+msgstr "Câu lệnh đăng nhập (LOGIN CMD) không rõ"
-#, fuzzy
msgid "Unknown CLIENT CMD"
-msgstr "Lý do không rõ"
+msgstr "Câu lệnh ứng dụng khách (CLIENT CMD) không rõ"
#, c-format
msgid "%d has declined the file %s"
@@ -8043,9 +7900,9 @@ msgstr "%d đã từ chối nhận tập tin %s"
msgid "File Send"
msgstr "Gửi tập tin"
-#, fuzzy, c-format
+#, c-format
msgid "%d cancelled the transfer of %s"
-msgstr "%d đã thôi truyền %s"
+msgstr "%d đã thôi tiến trình truyền %s"
#, c-format
msgid "<b>Group Title:</b> %s<br>"
@@ -9014,9 +8871,8 @@ msgstr "Kiểu khoá công không được hỗ trợ"
msgid "Disconnected by server"
msgstr "Bị máy phục vụ ngắt kết nối"
-#, fuzzy
msgid "Error connecting to SILC Server"
-msgstr "Gặp lỗi trong khi kết nối tới máy phục vụ SILC"
+msgstr "Gặp lỗi khi kết nối tới máy phục vụ SILC"
msgid "Key Exchange failed"
msgstr "Trao đổi mã khoá thất bại"
@@ -9030,27 +8886,25 @@ msgstr ""
msgid "Performing key exchange"
msgstr "Đang thực hiện trao đổi mã khoá"
-#, fuzzy
msgid "Unable to load SILC key pair"
-msgstr "Không thể nạp cặp khoá SILC"
+msgstr "Không thể nạp được cặp khoá SILC"
#. Progress
msgid "Connecting to SILC Server"
msgstr "Đang kết nối tới máy phục vụ SILC"
msgid "Out of memory"
-msgstr "Tràn bộ nhớ"
+msgstr "Không đủ bộ nhớ"
-#, fuzzy
msgid "Unable to initialize SILC protocol"
-msgstr "Không thể khởi tạo giao thức SILC"
+msgstr "Không thể khởi tạo được giao thức SILC"
msgid "Error loading SILC key pair"
msgstr "Lỗi nạp cặp khoá SILC"
-#, fuzzy, c-format
+#, c-format
msgid "Download %s: %s"
-msgstr "Người dùng trên %s: %s"
+msgstr "Tải về %s: %s"
msgid "Your Current Mood"
msgstr "Tâm trạng hiện thời của bạn"
@@ -9348,9 +9202,8 @@ msgstr "Ký điện và thẩm tra mọi thông điệp"
msgid "Creating SILC key pair..."
msgstr "Đang tạo cặp khoá SILC..."
-#, fuzzy
msgid "Unable to create SILC key pair"
-msgstr "Không thể tạo cặp khoá SILC\n"
+msgstr "Không thể tạo cặp khoá SILC"
#. Hint for translators: Please check the tabulator width here and in
#. the next strings (short strings: 2 tabs, longer strings 1 tab,
@@ -9488,34 +9341,30 @@ msgstr "Thất bại: Cookie không hợp lệ"
msgid "Failure: Authentication failed"
msgstr "Thất bại: Không xác thực được"
-#, fuzzy
msgid "Unable to initialize SILC Client connection"
-msgstr "Không thể khởi tạo kết nối Khách SILC"
+msgstr "Không thể khởi tạo được kết nối Khách SILC"
msgid "John Noname"
-msgstr "Tham gia Không_tên"
+msgstr "Nguyễn Văn Không_tên"
-#, fuzzy, c-format
+#, c-format
msgid "Unable to load SILC key pair: %s"
-msgstr "Không thể nạp cặp khoá SILC: %s"
+msgstr "Không thể nạp được cặp khoá SILC: %s"
msgid "Unable to create connection"
msgstr "Không thể tạo kết nối"
-#, fuzzy
msgid "Unknown server response"
-msgstr "Không rõ đáp ứng từ máy phục vụ"
+msgstr "Không nhận ra đáp ứng máy phục vụ"
-#, fuzzy
msgid "Unable to create listen socket"
-msgstr "Không thể tạo ổ cắm"
+msgstr "Không thể tạo ổ cắm lắng nghe"
msgid "SIP usernames may not contain whitespaces or @ symbols"
msgstr "Tên người dùng SIP không được chứa dấu cách hay ký hiệu @"
-#, fuzzy
msgid "SIP connect server not specified"
-msgstr "Cổng máy phục vụ"
+msgstr "Chưa ghi rõ máy phục vụ kết nối SIP"
#. *< type
#. *< ui_requirement
@@ -9559,9 +9408,8 @@ msgstr "list: Liệt kê các phòng trên mạng Yahoo"
msgid "doodle: Request user to start a Doodle session"
msgstr "doodle: Yêu cầu người dùng bắt đầu vẽ hình"
-#, fuzzy
msgid "Yahoo ID..."
-msgstr "ID Yahoo"
+msgstr "Yahoo ID..."
#. *< type
#. *< ui_requirement
@@ -9573,9 +9421,8 @@ msgstr "ID Yahoo"
#. *< version
#. * summary
#. * description
-#, fuzzy
msgid "Yahoo! Protocol Plugin"
-msgstr "Phần bổ sung giao thức Yahoo"
+msgstr "Phần bổ sung giao thức Yahoo!"
msgid "Pager server"
msgstr "Máy phục vụ nhắn tin"
@@ -9596,7 +9443,7 @@ msgid "Ignore conference and chatroom invitations"
msgstr "Lời đi các lời mời vào hội thảo hay phòng chát"
msgid "Use account proxy for SSL connections"
-msgstr ""
+msgstr "Dùng ủy nhiệm tài khoản cho kết nối SSL"
msgid "Chat room list URL"
msgstr "URL đến danh sách phòng chát"
@@ -9607,9 +9454,8 @@ msgstr "Máy phục vụ chát Yahoo"
msgid "Yahoo Chat port"
msgstr "Cổng chát Yahoo"
-#, fuzzy
msgid "Yahoo JAPAN ID..."
-msgstr "ID Yahoo"
+msgstr "ID Yahoo NHẬT BẢN..."
#. *< type
#. *< ui_requirement
@@ -9621,16 +9467,15 @@ msgstr "ID Yahoo"
#. *< version
#. * summary
#. * description
-#, fuzzy
msgid "Yahoo! JAPAN Protocol Plugin"
-msgstr "Phần bổ sung giao thức Yahoo"
+msgstr "Phần bổ sung giao thức Yahoo NHẬT BẢN"
#, c-format
msgid "%s has sent you a webcam invite, which is not yet supported."
msgstr "%s mời bạn xem máy ảnh Web, mà chưa được hỗ trợ."
msgid "Your SMS was not delivered"
-msgstr ""
+msgstr "SMS của bạn đã không được phát"
msgid "Your Yahoo! message did not get sent."
msgstr "Tin nhẳn Yahoo! của bạn đã không được gửi."
@@ -9657,32 +9502,28 @@ msgid "Add buddy rejected"
msgstr "Thêm bạn chát bị từ chối"
#. Some error in the received stream
-#, fuzzy
msgid "Received invalid data"
-msgstr "Nhận dữ liệu không hợp lệ khi kết nối tới máy phục vụ."
+msgstr "Nhận được dữ liệu không hợp lệ"
#. security lock from too many failed login attempts
-#, fuzzy
msgid ""
"Account locked: Too many failed login attempts. Logging into the Yahoo! "
"website may fix this."
msgstr ""
-"Mã lỗi không rõ %d. Đăng nhập vào địa chỉ Web của Yahoo có thể giúp khắc "
-"phục."
+"Tài khoản bị khoá: quá nhiều lần đăng nhập không thành công. Đăng nhập vào "
+"địa chỉ Web của Yahoo có thể giúp khắc phục."
#. indicates a lock of some description
-#, fuzzy
msgid ""
"Account locked: Unknown reason. Logging into the Yahoo! website may fix "
"this."
msgstr ""
-"Mã lỗi không rõ %d. Đăng nhập vào địa chỉ Web của Yahoo có thể giúp khắc "
-"phục."
+"Tài khoản bị khoá: không biết sao. Đăng nhập vào địa chỉ Web của Yahoo có "
+"thể giúp khắc phục."
#. username or password missing
-#, fuzzy
msgid "Username or password missing"
-msgstr "Tên người dùng hoặc mật khẩu sai"
+msgstr "Còn thiếu tên người dùng hay mật khẩu"
#, c-format
msgid ""
@@ -9707,63 +9548,57 @@ msgstr ""
msgid "Ignore buddy?"
msgstr "Lờ bỏ bạn chát?"
-#, fuzzy
msgid "Invalid username or password"
-msgstr "Tên người dùng hoặc mật khẩu sai"
+msgstr "Sai gõ tên người dùng hay mật khẩu"
-#, fuzzy
msgid ""
"Your account has been locked due to too many failed login attempts. Please "
"try logging into the Yahoo! website."
msgstr ""
-"Mã lỗi không rõ %d. Đăng nhập vào địa chỉ Web của Yahoo có thể giúp khắc "
-"phục."
+"Tài khoản của bạn bị khoá do quá nhiều lần đăng nhập không thành công. Đăng "
+"nhập vào địa chỉ Web của Yahoo có thể giúp khắc phục."
#, c-format
msgid "Unknown error 52. Reconnecting should fix this."
-msgstr ""
+msgstr "Gặp lỗi không rõ 52. Tái kết nối nên giúp khắc phục."
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 ""
+"Lỗi 1013: bạn đã gõ một tên người dùng không hợp lệ. Nguyên nhân thường gặp "
+"nhất của lỗi này là gõ địa chỉ thư điện tử thay cho ID Yahoo."
#, c-format
msgid "Unknown error number %d. Logging into the Yahoo! website may fix this."
msgstr ""
-"Mã lỗi không rõ %d. Đăng nhập vào địa chỉ Web của Yahoo có thể giúp khắc "
+"Gặp lỗi không rõ số %d. Đăng nhập vào địa chỉ Web của Yahoo có thể giúp khắc "
"phục."
-#, fuzzy, c-format
+#, c-format
msgid "Unable to add buddy %s to group %s to the server list on account %s."
msgstr ""
-"Không thể thêm bạn chát %s vào nhóm %s trong danh sach máy phục vụ của tài "
+"Không thể thêm bạn chát %s vào nhóm %s trong danh sach máy phục vụ trên tài "
"khoản %s."
-#, fuzzy
msgid "Unable to add buddy to server list"
-msgstr "Không thể thêm bạn chát vào danh sách máy phục vụ"
+msgstr "Không thể thêm bạn chát vào danh sách các máy phục vụ"
# Audible là nhà cung cấp thông tin bằng âm thanh: tên, đừng dịch.
#, c-format
msgid "[ Audible %s/%s/%s.swf ] %s"
msgstr "[ Audible %s/%s/%s.swf ] %s"
-#, fuzzy
msgid "Received unexpected HTTP response from server"
-msgstr "Nhận được đáp ứng HTTP bất thường từ máy phục vụ."
+msgstr "Nhận được đáp ứng HTTP bất thường từ máy phục vụ"
-#, fuzzy, c-format
+#, c-format
msgid "Lost connection with %s: %s"
-msgstr ""
-"Mất kết nối với %s:\n"
-"%s"
+msgstr "Mất kết nối với %s: %s"
-#, fuzzy, c-format
+#, c-format
msgid "Unable to establish a connection with %s: %s"
-msgstr ""
-"Không thể thiết lập kết nối đến máy phục vụ :\n"
-"%s"
+msgstr "Không thể thiết lập kết nối với %s: %s"
msgid "Not at Home"
msgstr "Không có ở nhà"
@@ -9811,10 +9646,10 @@ msgid "Start Doodling"
msgstr "Bắt đầu vẽ"
msgid "Select the ID you want to activate"
-msgstr ""
+msgstr "Hãy chọn ID cần kích hoạt"
msgid "Join whom in chat?"
-msgstr "Tham gia với ai trong chat?"
+msgstr "Tham gia với ai trong chát?"
msgid "Activate ID..."
msgstr "Kích hoạt ID..."
@@ -9825,6 +9660,15 @@ msgstr "Tham gia với người dùng trong chat..."
msgid "Open Inbox"
msgstr "Mở hộp thư đến"
+msgid "Can't send SMS. Unable to obtain mobile carrier."
+msgstr "Không thể gửi SMS. Không thể lấy mạng truyền sóng di động."
+
+msgid "Can't send SMS. Unknown mobile carrier."
+msgstr "Không thể gửi SMS. Không nhận ra mạng truyền sóng di động."
+
+msgid "Getting mobile carrier to send the SMS."
+msgstr "Đang lấy mạng truyền sóng di động để gửi SMS."
+
#. Write a local message to this conversation showing that a request for a
#. * Doodle session has been made
#.
@@ -9887,12 +9731,9 @@ msgstr "Liên kết ưa thích 3"
msgid "Last Update"
msgstr "Cập nhật lần cuối"
-#, fuzzy
msgid ""
"This profile is in a language or format that is not supported at this time."
-msgstr ""
-"Tiếc là hình như lý lịch này được viết bằng ngôn ngữ hay định dạng chưa được "
-"hỗ trợ."
+msgstr "Hồ sơ này được viết bằng ngôn ngữ hay định dạng chưa được hỗ trợ."
msgid ""
"Could not retrieve the user's profile. This most likely is a temporary "
@@ -9913,16 +9754,16 @@ msgstr ""
msgid "The user's profile is empty."
msgstr "Lý lịch người dùng này trống."
-#, fuzzy, c-format
+#, c-format
msgid "%s has declined to join."
-msgstr "%s đã đăng nhập."
+msgstr "%s đã khước từ lời mời tham gia."
msgid "Failed to join chat"
msgstr "Không tham gia chát được"
#. -6
msgid "Unknown room"
-msgstr "Lỗi không rõ"
+msgstr "Phòng không được nhận ra"
#. -15
msgid "Maybe the room is full"
@@ -9967,9 +9808,8 @@ msgstr "Không thể lấy danh sách phòng."
msgid "User Rooms"
msgstr "Phòng người dùng"
-#, fuzzy
msgid "Connection problem with the YCHT server"
-msgstr "Kết nối có vấn đề với máy phục vụ YCHT."
+msgstr "Có vấn đề kết nối với máy phục vụ YCHT"
msgid ""
"(There was an error converting this message.\t Check the 'Encoding' option "
@@ -10097,19 +9937,19 @@ msgstr "Địa hạt"
msgid "Exposure"
msgstr "Phơi sáng"
-#, fuzzy, c-format
+#, c-format
msgid "Unable to parse response from HTTP proxy: %s"
-msgstr "Không thể phân tích đáp ứng từ ủy nhiệm HTTP: %s\n"
+msgstr "Không thể phân tích cú pháp của đáp ứng từ ủy nhiệm HTTP: %s"
#, c-format
msgid "HTTP proxy connection error %d"
msgstr "Lỗi kết nối ủy nhiệm HTTP %d"
-#, fuzzy, c-format
+#, c-format
msgid "Access denied: HTTP proxy server forbids port %d tunneling"
msgstr ""
"Truy cập bị từ chối: máy phục vụ ủy nhiệm HTTP cấm đào đường hầm xuyên qua "
-"cổng %d."
+"cổng %d"
#, c-format
msgid "Error resolving %s"
@@ -10286,9 +10126,9 @@ msgstr "Không thể mở %s: chuyển hướng quá nhiều lần"
msgid "Unable to connect to %s"
msgstr "Không thể kết nối tới %s"
-#, fuzzy, c-format
+#, c-format
msgid "Error reading from %s: response too long (%d bytes limit)"
-msgstr "Lỗi đọc từ %s: %s"
+msgstr "Gặp lỗi khi đọc từ %s: đáp ứng quá dài (giới hạn %d byte)"
#, c-format
msgid ""
@@ -10339,15 +10179,15 @@ msgid "Connection refused."
msgstr "Kết nối bị từ chối."
#. 10048
-#, fuzzy, c-format
+#, c-format
msgid "Address already in use."
-msgstr "Tên chát này đang được dùng"
+msgstr "Địa chỉ vẫn còn được dùng."
#, c-format
msgid "Error Reading %s"
msgstr "Lỗi đọc %s"
-#, fuzzy, c-format
+#, c-format
msgid ""
"An error was encountered reading your %s. The file has not been loaded, and "
"the old file has been renamed to %s~."
@@ -10397,9 +10237,8 @@ msgstr "Thông báo thư _mới"
msgid "Use this buddy _icon for this account:"
msgstr "Dùng biểu tượng bạn chát cho tài khoản này:"
-#, fuzzy
msgid "Ad_vanced"
-msgstr "Cấp c_ao"
+msgstr "Cấp ca_o"
msgid "Use GNOME Proxy Settings"
msgstr "Dùng thiết lập ủy nhiệm GNOME"
@@ -10461,9 +10300,8 @@ msgstr "Cơ _bản"
msgid "Create _this new account on the server"
msgstr "_Tạo tài khoản mới này trên máy phục vụ"
-#, fuzzy
msgid "P_roxy"
-msgstr "Ủy nhiệm"
+msgstr "Ủ_y nhiệm"
msgid "Enabled"
msgstr "Đã bật"
@@ -10471,7 +10309,7 @@ msgstr "Đã bật"
msgid "Protocol"
msgstr "Giao thức"
-#, fuzzy, c-format
+#, c-format
msgid ""
"<span size='larger' weight='bold'>Welcome to %s!</span>\n"
"\n"
@@ -10486,148 +10324,136 @@ msgstr ""
"<span size='larger' weight='bold'>Chào mừng đến %s!</span>\n"
"\n"
"Bạn chưa cấu hình tài khoản tin nhắn. Để bắt đầu kết nối với %s, bấm cái nút "
-"<b>Thêm</b> bên dưới và cấu hình tài khoản thứ nhất của bạn. Muốn %s kết nối "
-"đến nhiều tài khoản thì bấm <b>Thêm</b> lần nữa để cấu hình mỗi điều.\n"
+"<b>Thêm...</b> bên dưới và cấu hình tài khoản đầu tiên của mình. Muốn %s kết "
+"nối đến nhiều tài khoản tin nhắn thì bấm nút <b>Thêm...</b> lần nữa để cấu "
+"hình cả.\n"
"\n"
-"Bạn vẫn có thể trở về cửa sổ này để thêm, chỉnh sửa hay gỡ bỏ tài khoản, "
-"bằng cách chọn mục <b>Tài khoản > Thêm/Sửa</b> trong cửa sổ Danh sách bạn bè."
+"Bạn vẫn có thể trở về cửa sổ này để thêm, chỉnh sửa hay gỡ bỏ tài khoản, sử "
+"dụng mục <b>Tài khoản > Quản lý Tài khoản</b> trong cửa sổ Danh sách Bạn chát"
#. Buddy List
msgid "Background Color"
msgstr "Màu nền"
-#, fuzzy
msgid "The background color for the buddy list"
-msgstr "Nhóm này đã được thêm vào danh sách bạn bè của bạn"
+msgstr "Màu nền của danh sách bạn chát"
-#, fuzzy
msgid "Layout"
-msgstr "Tiếng Lào"
+msgstr "Bố trí"
msgid "The layout of icons, name, and status of the buddy list"
-msgstr ""
+msgstr "Cách sắp đặt các biểu tượng; tên và trạng thái của danh sách bạn chát"
#. Group
#. Note to translators: These two strings refer to the background color
#. of a buddy list group when in its expanded state
-#, fuzzy
msgid "Expanded Background Color"
-msgstr "Màu nền"
+msgstr "Màu nền giãn ra"
msgid "The background color of an expanded group"
-msgstr ""
+msgstr "Màu nền của một nhóm đã giãn ra"
#. Note to translators: These two strings refer to the font and color
#. of a buddy list group when in its expanded state
-#, fuzzy
msgid "Expanded Text"
-msgstr "Giãn _ra"
+msgstr "Chú giải giãn ra"
msgid "The text information for when a group is expanded"
-msgstr ""
+msgstr "Chú giải khi một nhóm được giãn ra"
#. Note to translators: These two strings refer to the background color
#. of a buddy list group when in its collapsed state
-#, fuzzy
msgid "Collapsed Background Color"
-msgstr "Chọn màu nền"
+msgstr "Màu nền co lại"
msgid "The background color of a collapsed group"
-msgstr ""
+msgstr "Màu nền của một nhóm đa co lại"
#. Note to translators: These two strings refer to the font and color
#. of a buddy list group when in its collapsed state
-#, fuzzy
msgid "Collapsed Text"
-msgstr "_Co lại"
+msgstr "Chú giải co lại"
msgid "The text information for when a group is collapsed"
-msgstr ""
+msgstr "Chú giải khi một nhóm được co lại"
#. Buddy
#. Note to translators: These two strings refer to the background color
#. of a buddy list contact or chat room
-#, fuzzy
msgid "Contact/Chat Background Color"
-msgstr "Chọn màu nền"
+msgstr "Màu nền Liên lạc / Chát"
msgid "The background color of a contact or chat"
-msgstr ""
+msgstr "Màu nền của một liên lạc hay cuộc trò chuyện"
#. Note to translators: These two strings refer to the font and color
#. of a buddy list contact when in its expanded state
-#, fuzzy
msgid "Contact Text"
-msgstr "Lối tắt"
+msgstr "Chú giải Liên lạc"
msgid "The text information for when a contact is expanded"
-msgstr ""
+msgstr "Chú giải khi một liên lạc được giãn ra"
#. 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 "Trực tuyến"
+msgstr "Chú giải Trực tuyến"
msgid "The text information for when a buddy is online"
-msgstr ""
+msgstr "Chú giải khi một bạn chát có kết nối hoạt động"
#. Note to translators: These two strings refer to the font and color
#. of a buddy list buddy when it is away
-#, fuzzy
msgid "Away Text"
-msgstr "Vắng mặt"
+msgstr "Chú giải Vắng mặt"
msgid "The text information for when a buddy is away"
-msgstr ""
+msgstr "Chú giải khi một bạn chát có vắng mặt"
#. 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 "Ngoại tuyến"
+msgstr "Chú giải Ngoại tuyến"
-#, fuzzy
msgid "The text information for when a buddy is offline"
-msgstr "Đổi thông tin người dùng cho %s"
+msgstr "Chú giải khi một bạn chát không có kết nối hoạt động"
#. Note to translators: These two strings refer to the font and color
#. of a buddy list buddy when it is idle
-#, fuzzy
msgid "Idle Text"
-msgstr "Kiểm tra tâm trạng"
+msgstr "Chú giải Nghỉ"
msgid "The text information for when a buddy is idle"
-msgstr ""
+msgstr "Chú giải khi một bạn chát đang nghỉ"
#. Note to translators: These two strings refer to the font and color
#. of a buddy list buddy when they have sent you a new message
-#, fuzzy
msgid "Message Text"
-msgstr "Gửi tin nhẳn"
+msgstr "Chú giải Tin nhẳn"
msgid "The text information for when a buddy has an unread message"
-msgstr ""
+msgstr "Chú giải khi một bạn chát có tin nhẳn chưa đọc"
#. Note to translators: These two strings refer to the font and color
#. of a buddy list buddy when they have sent you a new message
msgid "Message (Nick Said) Text"
-msgstr ""
+msgstr "Chú giải Tin nhẳn (Nói tên hiệu)"
msgid ""
"The text information for when a chat has an unread message that mentions "
"your nickname"
msgstr ""
+"Chú giải khi một cuộc trò chuyện chứa một tin nhẳn chưa đọc mà nói tên hiệu "
+"của bạn"
-#, fuzzy
msgid "The text information for a buddy's status"
-msgstr "Đổi thông tin người dùng cho %s"
+msgstr "Chú giải về trạng thái của bạn chát"
#, c-format
msgid "You have %d contact named %s. Would you like to merge them?"
msgid_plural ""
"You currently have %d contacts named %s. Would you like to merge them?"
-msgstr[0] "Bạn có %d liên lạc tên %s. Muốn trộn với nhau không?"
+msgstr[0] "Bạn có %d liên lạc tên %s. Muốn gộp lại không?"
msgid ""
"Merging these contacts will cause them to share a single entry on the buddy "
@@ -10641,9 +10467,8 @@ msgstr ""
msgid "Please update the necessary fields."
msgstr "Hãy cập nhật các trường cần thiết."
-#, fuzzy
msgid "A_ccount"
-msgstr "Tài khoản"
+msgstr "Tài kh_oản"
msgid ""
"Please enter the appropriate information about the chat you would like to "
@@ -10668,16 +10493,14 @@ msgstr "Lấy thông t_in"
msgid "I_M"
msgstr "_Tin nhắn"
-#, fuzzy
msgid "_Audio Call"
-msgstr "Thê_m chát"
+msgstr "Gọi th_oại"
msgid "Audio/_Video Call"
-msgstr ""
+msgstr "Gọi thoại/_phim"
-#, fuzzy
msgid "_Video Call"
-msgstr "Trò chuyện ảnh động"
+msgstr "Gọi _phim"
msgid "_Send File..."
msgstr "_Gửi tập tin..."
@@ -10688,11 +10511,9 @@ msgstr "Thêm thông _báo bạn chát..."
msgid "View _Log"
msgstr "_Xem bản ghi"
-#, fuzzy
msgid "Hide When Offline"
msgstr "Ẩn khi ngoại tuyến"
-#, fuzzy
msgid "Show When Offline"
msgstr "Hiện khi ngoại tuyến"
@@ -10820,18 +10641,17 @@ msgstr "/Công cụ/Thông _báo bạn chát"
msgid "/Tools/_Certificates"
msgstr "/Công cụ/_Chứng nhận"
-#, fuzzy
msgid "/Tools/Custom Smile_ys"
-msgstr "/Công cụ/Hình cườ_i"
+msgstr "/Công cụ/Hình cười _riêng"
msgid "/Tools/Plu_gins"
-msgstr "/Công cụ/_Phần bổ sung"
+msgstr "/Công cụ/Phần bổ sun_g"
msgid "/Tools/Pr_eferences"
msgstr "/Công cụ/Tù_y chỉnh"
msgid "/Tools/Pr_ivacy"
-msgstr "/Công cụ/_Riêng tư"
+msgstr "/Công cụ/R_iêng tư"
msgid "/Tools/_File Transfers"
msgstr "/Công cụ/_Truyền tập tin"
@@ -10950,11 +10770,11 @@ msgid "By status"
msgstr "Theo trạng thái"
msgid "By recent log activity"
-msgstr ""
+msgstr "Theo hoạt động vừa ghi lưu"
#, c-format
msgid "%s disconnected"
-msgstr "%s đã ngắt kết nối."
+msgstr "%s bị ngắt kết nối"
#, c-format
msgid "%s disabled"
@@ -10967,7 +10787,7 @@ msgid "Re-enable"
msgstr "Bật lại"
msgid "SSL FAQs"
-msgstr ""
+msgstr "Hỏi Đáp SSL"
msgid "Welcome back!"
msgstr "Chào mừng lại !"
@@ -10976,7 +10796,7 @@ msgstr "Chào mừng lại !"
msgid "%d account was disabled because you signed on from another location:"
msgid_plural ""
"%d accounts were disabled because you signed on from another location:"
-msgstr[0] "%d tài khoản đã bị tắt vì bạn cũng đăng nhập từ chỗ khác."
+msgstr[0] "%d tài khoản đã bị tắt vì bạn cũng đăng nhập từ địa chỉ khác:"
msgid "<b>Username:</b>"
msgstr "<b>Tên người dùng:</b>"
@@ -11058,13 +10878,11 @@ msgstr "_Bí danh:"
msgid "_Group:"
msgstr "_Nhóm:"
-#, fuzzy
msgid "Auto_join when account connects."
msgstr "Tự động tham _gia một khi tài khoản kết nối."
-#, fuzzy
msgid "_Remain in chat after window is closed."
-msgstr "Ẩn c_hát khi cửa sổ bị đóng."
+msgstr "Còn lại t_rong chát sau khi cửa sổ bị đóng."
msgid "Please enter the name of the group to be added."
msgstr "Hãy nhập tên của nhóm cần thêm vào."
@@ -11093,9 +10911,8 @@ msgstr "/Công cụ"
msgid "/Buddies/Sort Buddies"
msgstr "/Bạn chát/Sắp xếp bạn chát"
-#, fuzzy
msgid "Type the host name for this certificate."
-msgstr "Gõ tên máy cho đó có chứng nhận này."
+msgstr "Gõ tên máy cho chứng nhận này."
#. Widget creation function
msgid "SSL Servers"
@@ -11144,9 +10961,8 @@ msgstr "Lờ"
msgid "Get Away Message"
msgstr "Lấy thông điệp vắng mặt"
-#, fuzzy
msgid "Last Said"
-msgstr "Nói lần cuối"
+msgstr "Nói cuối"
msgid "Unable to save icon file to disk."
msgstr "Không thể lưu tập tin biểu tượng vào đĩa."
@@ -11179,9 +10995,8 @@ msgstr "/_Cuộc thoại"
msgid "/Conversation/New Instant _Message..."
msgstr "/Cuộc thoại/Tin nhắn _mới..."
-#, fuzzy
msgid "/Conversation/Join a _Chat..."
-msgstr "/Cuộc thoại/Mờ_i..."
+msgstr "/Cuộc thoại/Tham gia _chát..."
msgid "/Conversation/_Find..."
msgstr "/Cuộc thoại/_Tìm..."
@@ -11195,24 +11010,20 @@ msgstr "/Cuộc thoại/_Lưu dạng..."
msgid "/Conversation/Clea_r Scrollback"
msgstr "/Cuộc thoại/Gột _vùng cuộn ngược"
-#, fuzzy
msgid "/Conversation/M_edia"
-msgstr "/Cuộc thoại/Nữ_a"
+msgstr "/Cuộc thoại/_Phương tiện"
-#, fuzzy
msgid "/Conversation/Media/_Audio Call"
-msgstr "/Cuộc thoại/Nữ_a"
+msgstr "/Cuộc thoại/Phương tiện/Gọi th_oại"
-#, fuzzy
msgid "/Conversation/Media/_Video Call"
-msgstr "/Cuộc thoại/Nữ_a"
+msgstr "/Cuộc thoại/Phương tiện/Gọi _phim"
-#, fuzzy
msgid "/Conversation/Media/Audio\\/Video _Call"
-msgstr "/Cuộc thoại/_Xem bản ghi"
+msgstr "/Cuộc thoại/Phương tiện/Gọi thoại\\/_phim"
msgid "/Conversation/Se_nd File..."
-msgstr "/Cuộc thoại/_Gửi tập tin..."
+msgstr "/Cuộc thoại/Gửi tập ti_n..."
msgid "/Conversation/Add Buddy _Pounce..."
msgstr "/Cuộc thoại/Thêm thông bá_o bạn chát..."
@@ -11283,17 +11094,14 @@ msgstr "/Cuộc thoại"
msgid "/Conversation/View Log"
msgstr "/Cuộc thoại/Xem bản ghi"
-#, fuzzy
msgid "/Conversation/Media/Audio Call"
-msgstr "/Cuộc thoại/_Nữa"
+msgstr "/Cuộc thoại/Phương tiện/Gọi thoại"
-#, fuzzy
msgid "/Conversation/Media/Video Call"
-msgstr "/Cuộc thoại/Xem bản ghi"
+msgstr "/Cuộc thoại/Phương tiện/Gọi phim"
-#, fuzzy
msgid "/Conversation/Media/Audio\\/Video Call"
-msgstr "/Cuộc thoại/_Nữa"
+msgstr "/Cuộc thoại/Phương tiện/Gọi thoại\\/phim"
msgid "/Conversation/Send File..."
msgstr "/Cuộc thoại/Gửi tập tin..."
@@ -11467,25 +11275,23 @@ msgid "Fatal Error"
msgstr "Lỗi nghiêm trọng"
msgid "bug master"
-msgstr ""
+msgstr "chủ lỗi"
-#, fuzzy
msgid "artist"
-msgstr "Nghệ sĩ"
+msgstr "nghệ sĩ"
#. feel free to not translate this
msgid "Ka-Hing Cheung"
-msgstr ""
+msgstr "Ka-Hing Cheung"
msgid "voice and video"
-msgstr ""
+msgstr "thoại và phim"
msgid "support"
msgstr "hỗ trợ"
-#, fuzzy
msgid "webmaster"
-msgstr "nhà phát triển và chủ Web"
+msgstr "chủ Web"
msgid "Senior Contributor/QA"
msgstr "Người đóng góp cấp cao/ tin chắc chất lượng"
@@ -11507,7 +11313,7 @@ msgid "support/QA"
msgstr "hỗ trợ / tin chắc chất lượng"
msgid "XMPP"
-msgstr ""
+msgstr "XMPP"
msgid "original author"
msgstr "tác giả đầu tiên"
@@ -11573,7 +11379,7 @@ msgid "Estonian"
msgstr "Tiếng E-x-tô-ni"
msgid "Basque"
-msgstr ""
+msgstr "Tiếng Ba-x-quợ"
msgid "Persian"
msgstr "Tiếng Ba Tư"
@@ -11584,9 +11390,8 @@ msgstr "Tiếng Phần Lan"
msgid "French"
msgstr "Tiếng Pháp"
-#, fuzzy
msgid "Irish"
-msgstr "Tiếng Kuổ-đít"
+msgstr "Tiếng Ai-len"
msgid "Galician"
msgstr "Tiếng Ga-li-xi"
@@ -11606,9 +11411,8 @@ msgstr "Tiếng Hin-đi"
msgid "Hungarian"
msgstr "Tiếng Hung-ga-ri"
-#, fuzzy
msgid "Armenian"
-msgstr "Tiếng Ru-ma-ni"
+msgstr "Tiếng Ác-mê-ni"
msgid "Indonesian"
msgstr "Tiếng Nam Dương"
@@ -11625,9 +11429,8 @@ msgstr "Tiếng Gi-oa-gi-a"
msgid "Ubuntu Georgian Translators"
msgstr "Nhóm Dịch Giả Gi-oa-gi-a Ubuntu"
-#, fuzzy
msgid "Khmer"
-msgstr "Khác"
+msgstr "Tiếng Khơ-me"
msgid "Kannada"
msgstr "Tiếng Kan-na-đa"
@@ -11636,7 +11439,7 @@ msgid "Kannada Translation team"
msgstr "Nhóm Dịch Kan-na-đa"
msgid "Korean"
-msgstr "Tiếng Hàn"
+msgstr "Tiếng Triều Tiên"
msgid "Kurdish"
msgstr "Tiếng Kuổ-đít"
@@ -11650,9 +11453,8 @@ msgstr "Tiếng Li-tu-a-ni"
msgid "Macedonian"
msgstr "Tiếng Ma-xê-đô-ni"
-#, fuzzy
msgid "Mongolian"
-msgstr "Tiếng Ma-xê-đô-ni"
+msgstr "Tiếng Mông Cổ"
msgid "Bokmål Norwegian"
msgstr "Tiếng Na-uy (Bóc-măn)"
@@ -11709,7 +11511,7 @@ msgid "Swedish"
msgstr "Tiếng Thụy Điển"
msgid "Swahili"
-msgstr ""
+msgstr "Tiếng Xouă-hi-li"
msgid "Tamil"
msgstr "Tiếng Ta-min"
@@ -11775,6 +11577,8 @@ msgid ""
"<FONT SIZE=\"4\">FAQ:</FONT> <A HREF=\"http://developer.pidgin.im/wiki/FAQ"
"\">http://developer.pidgin.im/wiki/FAQ</A><BR/><BR/>"
msgstr ""
+"<FONT SIZE=\"4\">Hỏi Đáp:</FONT> <A HREF=\"http://developer.pidgin.im/wiki/"
+"FAQ\">http://developer.pidgin.im/wiki/FAQ</A><BR/><BR/>"
#, c-format
msgid ""
@@ -11785,15 +11589,25 @@ msgid ""
"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\">Sự giúp đỡ từ các người dùng Pidgin khác:</font> <a href="
+"\"mailto:support@pidgin.im\">support@pidgin.im</a><br/>Đây là một hộp thư "
+"chung loại <b>công cộng</b> ! (<a href=\"http://pidgin.im/pipermail/support/"
+"\">kho thư</a>)<br/>Tiếc là chúng tôi không thể giúp về giao thức hay phần "
+"bổ sung loại nhóm ba, chỉ với chương trình Pidgin chính nó.<br/>Ngôn ngữ "
+"chính của hộp thư chung nàu là <b>tiếng Anh</b>. Bạn cũng có thể gửi thư "
+"bằng một ngôn ngữ khác, nhưng mà đáp ứng có thể không có ích. (Dịch giả: để "
+"hỏi câu về phần mềm nguồn mở bằng tiếng Việt, hãy tham gia <a href=\"http://"
+"forum.vnoss.org/\">Diễn đàn VNOSS</a> hay <a href=\"http://lists.hanoilug."
+"org/listinfo\">hộp thư chung Hà Nội LUG</a>.)<br/><br/>"
-#, fuzzy, c-format
+#, c-format
msgid ""
"<FONT SIZE=\"4\">IRC Channel:</FONT> #pidgin on irc.freenode.net<BR><BR>"
-msgstr "<FONT SIZE=\"3\">IRC:</FONT> #pidgin trên irc.freenode.net<BR><BR>"
+msgstr "<FONT SIZE=\"4\">Kênh IRC:</FONT> #pidgin on irc.freenode.net<BR><BR>"
-#, fuzzy, c-format
+#, c-format
msgid "<FONT SIZE=\"4\">XMPP MUC:</FONT> devel@conference.pidgin.im<BR><BR>"
-msgstr "<FONT SIZE=\"3\">IRC:</FONT> #pidgin trên irc.freenode.net<BR><BR>"
+msgstr "<FONT SIZE=\"4\">XMPP MUC:</FONT> devel@conference.pidgin.im<BR><BR>"
msgid "Current Developers"
msgstr "Nhà phát triển hiện thời"
@@ -12027,19 +11841,17 @@ msgstr "Màu siêu liên kết"
msgid "Color to draw hyperlinks."
msgstr "Màu để vẽ siêu liên kết."
-#, fuzzy
msgid "Hyperlink visited color"
-msgstr "Màu siêu liên kết"
+msgstr "Màu siêu liên kết đã thăm"
-#, fuzzy
msgid "Color to draw hyperlink after it has been visited (or activated)."
-msgstr "Màu siêu liên kết nổi bật khi rê chuột qua"
+msgstr "Màu siêu liên kết nổi bật sau khi nó được thăm (hay kích hoạt)."
msgid "Hyperlink prelight color"
msgstr "Màu siêu liên kết tô sáng"
msgid "Color to draw hyperlinks when mouse is over them."
-msgstr "Màu siêu liên kết nổi bật khi rê chuột qua"
+msgstr "Màu siêu liên kết nổi bật khi rê chuột qua."
msgid "Sent Message Name Color"
msgstr "Màu tên tin nhẳn đã gửi"
@@ -12068,23 +11880,20 @@ msgstr "Màu để vẽ tên của tin nhẳn hành động."
msgid "Action Message Name Color for Whispered Message"
msgstr "Màu tên tin nhẳn hành động cho tin nhẳn thì thầm"
-#, fuzzy
msgid "Color to draw the name of a whispered action message."
-msgstr "Màu để vẽ tên của tin nhẳn hành động."
+msgstr "Màu để vẽ tên của tin nhẳn hành động thì thầm."
msgid "Whisper Message Name Color"
msgstr "Màu tên tin nhẳn thì thầm"
-#, fuzzy
msgid "Color to draw the name of a whispered message."
-msgstr "Màu để vẽ tên của tin nhẳn hành động."
+msgstr "Màu để vẽ tên của tin nhẳn thì thầm."
msgid "Typing notification color"
msgstr "Màu thông báo đang gõ"
-#, fuzzy
msgid "The color to use for the typing notification"
-msgstr "Màu cần dùng cho phông chữ thông báo đang gõ"
+msgstr "Màu cần dùng cho thông báo đang gõ"
msgid "Typing notification font"
msgstr "Phông chữ thông báo đang gõ"
@@ -12340,45 +12149,50 @@ msgid ""
"Usage: %s [OPTION]...\n"
"\n"
msgstr ""
+"Sử dụng: %s [TÙY_CHỌN]...\n"
+"\n"
msgid "DIR"
-msgstr ""
+msgstr "THƯ_MỤC"
msgid "use DIR for config files"
-msgstr ""
+msgstr "dùng thư mục này cho các tập tin cấu hình"
msgid "print debugging messages to stdout"
-msgstr ""
+msgstr "in các thông điệp gỡ rối ra đầu ra tiêu chuẩn"
msgid "force online, regardless of network status"
-msgstr ""
+msgstr "ép buộc trực tuyến, bất chấp trạng thái mạng"
msgid "display this help and exit"
-msgstr ""
+msgstr "hiển thị trợ giúp này, sau đó thoát"
-#, fuzzy
msgid "allow multiple instances"
-msgstr "Cho phép đăng ký nhiều lần đồng thời"
+msgstr "cho phép nhiều thể hiện đồng thời"
msgid "don't automatically login"
-msgstr ""
+msgstr "không tự động đăng nhập"
msgid "NAME"
-msgstr ""
+msgstr "TÊN"
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 ""
+"hiệu lực mỗi tài khoản được đưa ra\n"
+"\t(Tùy chọn TÊN cũng có thể ghi rõ (những) tài khoản cần dùng,\n"
+"\tmỗi cặp tên định giới bằng dấu phẩy.\n"
+"\tKhông có tuỳ chọn này thì chỉ hiệu lực tài khoản đầu tiên.)"
msgid "X display to use"
-msgstr ""
+msgstr "Màn hình X cần dùng"
msgid "display the current version and exit"
-msgstr ""
+msgstr "hiển thị phiên bản hiện thời, sau đó thoát"
-#, fuzzy, c-format
+#, c-format
msgid ""
"%s %s has segfaulted and attempted to dump a core file.\n"
"This is a bug in the software and has happened through\n"
@@ -12397,7 +12211,7 @@ msgstr ""
"Đây là một lỗi trong phần mềm, không phải do bạn.\n"
"\n"
"Nếu bạn có thể tạo lại trường hợp sụp đổ này,\n"
-"hãy thông báo nhà phát triển bằng cách báo cáo lỗi ở :\n"
+"hãy thông báo cho nhà phát triển bằng cách báo cáo lỗi ở :\n"
"%ssimpleticket/\n"
"\n"
"Hãy kiểm tra xem bạn ghi rõ chính xác những hành động\n"
@@ -12405,11 +12219,6 @@ msgstr ""
"từ tập tin lõi. Không biết cách lấy vết lùi\n"
"thì đọc những hướng dẫn ở :\n"
"%swiki/GetABacktrace\n"
-"\n"
-"Cần thêm sự giúp đớ thì gửi tin nhắn cho hoặc SeanEgn\n"
-"hoặc LSchiere (qua mạng AIM). Thông tin liên lạc\n"
-"với hai nhà phát triển này qua giao thức khác nằm ở :\n"
-"%swiki/DeveloperPages\n"
#. Translators may want to transliterate the name.
#. It is not to be translated.
@@ -12418,24 +12227,24 @@ msgstr "Pidgin"
#, c-format
msgid "Exiting because another libpurple client is already running.\n"
-msgstr ""
+msgstr "Đang thoát do một ứng dụng khách libpurple đang chạy.\n"
msgid "/_Media"
-msgstr ""
+msgstr "/_Phương tiện"
msgid "/Media/_Hangup"
-msgstr ""
+msgstr "/Phương tiện/_Ngừng nói"
#, c-format
msgid "%s wishes to start an audio/video session with you."
-msgstr ""
+msgstr "%s muốn bắt đầu một buổi hợp thoại/phim với bạn."
#, c-format
msgid "%s wishes to start a video session with you."
-msgstr ""
+msgstr "%s muốn bắt đầu một buổi hợp phim với bạn."
msgid "Incoming Call"
-msgstr ""
+msgstr "Gọi gửi đến"
msgid "_Pause"
msgstr "Tạm _dừng"
@@ -12466,9 +12275,8 @@ msgid ""
msgstr ""
"Đã chọn lệnh chạy trình duyệt « Bằng tay », nhưng không cung cấp lệnh nào."
-#, fuzzy
msgid "No message"
-msgstr "Thông điệp không rõ"
+msgstr "Không có tin nhẳn"
msgid "Open All Messages"
msgstr "Mở mọi tin nhẳn"
@@ -12476,16 +12284,14 @@ msgstr "Mở mọi tin nhẳn"
msgid "<span weight=\"bold\" size=\"larger\">You have mail!</span>"
msgstr "<span weight=\"bold\" size=\"larger\">Có thư mới !</span>"
-#, fuzzy
msgid "New Pounces"
-msgstr "Thông báo bạn thân mới"
+msgstr "Thông báo mới"
msgid "Dismiss"
-msgstr ""
+msgstr "Hủy"
-#, fuzzy
msgid "<span weight=\"bold\" size=\"larger\">You have pounced!</span>"
-msgstr "<span weight=\"bold\" size=\"larger\">Có thư mới !</span>"
+msgstr "<span weight=\"bold\" size=\"larger\">Có thông báo mới !</span>"
msgid "The following plugins will be unloaded."
msgstr "Những phần bổ sung theo đây sẽ được bỏ nạp."
@@ -12535,9 +12341,8 @@ msgstr "<b>Chi tiết về phần bổ sung</b>"
msgid "Select a file"
msgstr "Chọn tập tin"
-#, fuzzy
msgid "Modify Buddy Pounce"
-msgstr "Sửa thông báo bạn thân"
+msgstr "Sửa thông báo bạn chát"
#. Create the "Pounce on Whom" frame.
msgid "Pounce on Whom"
@@ -12612,61 +12417,73 @@ msgstr "_Lặp lại"
msgid "Pounce Target"
msgstr "Đích thông báo"
-#, fuzzy, c-format
+#, c-format
msgid "Started typing"
-msgstr "Bắt đầu gõ phím"
+msgstr "Đã bắt đầu gõ phím"
-#, fuzzy, c-format
+#, c-format
msgid "Paused while typing"
-msgstr "Tạm dừng khi gõ phím"
+msgstr "Đã tạm dừng khi gõ phím"
-#, fuzzy, c-format
+#, c-format
msgid "Signed on"
-msgstr "Đăng nhập"
+msgstr "Đã đăng nhập"
-#, fuzzy, c-format
+#, c-format
msgid "Returned from being idle"
-msgstr "%s hoạt động trở lại từ trạng thái nghỉ (%s)"
+msgstr "Đã trở lại từ trạng thái nghỉ"
-#, fuzzy, c-format
+#, c-format
msgid "Returned from being away"
-msgstr "Có mặt trở lại"
+msgstr "Đã có mặt trở lại"
-#, fuzzy, c-format
+#, c-format
msgid "Stopped typing"
-msgstr "Dừng gõ phím"
+msgstr "Đã dừng gõ phím"
-#, fuzzy, c-format
+#, c-format
msgid "Signed off"
-msgstr "Đăng xuất"
+msgstr "Đã đăng xuất"
-#, fuzzy, c-format
+#, c-format
msgid "Became idle"
msgstr "Đã rơi vào trạng thái nghỉ"
-#, fuzzy, c-format
+#, c-format
msgid "Went away"
-msgstr "Khi vắng mặt"
+msgstr "Đã vắng mặt"
-#, fuzzy, c-format
+#, c-format
msgid "Sent a message"
-msgstr "Gửi tin nhẳn"
+msgstr "Đã gửi tin nhẳn"
-#, fuzzy, c-format
+#, c-format
msgid "Unknown.... Please report this!"
-msgstr "Dữ kiện thông báo không rõ. Hãy ghi báo cáo việc này!"
+msgstr "Không rõ... Hãy ghi báo cáo trường hợp này!"
+
+msgid "(Custom)"
+msgstr "(Tự chọn)"
+
+msgid "(Default)"
+msgstr "(Mặc định)"
+
+msgid "The default Pidgin sound theme"
+msgstr "Sắc thái âm thanh Pidgin mặc định"
+
+msgid "The default Pidgin buddy list theme"
+msgstr "Sắc thái danh sách bạn chát Pidgin mặc định"
+
+msgid "The default Pidgin status icon theme"
+msgstr "Sắc thái biểu tượng trạng thái Pidgin mặc định"
-#, fuzzy
msgid "Theme failed to unpack."
-msgstr "Sắc thái hình cười không giải nén được."
+msgstr "Sắc thái không giải nén được."
-#, fuzzy
msgid "Theme failed to load."
-msgstr "Sắc thái hình cười không giải nén được."
+msgstr "Sắc thái không nạp được."
-#, fuzzy
msgid "Theme failed to copy."
-msgstr "Sắc thái hình cười không giải nén được."
+msgstr "Sắc thái không sao chép được."
msgid "Install Theme"
msgstr "Cài đặt sắc thái"
@@ -12688,9 +12505,8 @@ msgid "Cl_ose conversations with the Escape key"
msgstr "Đóng cuộc th_oại dùng phím Esc"
#. Buddy List Themes
-#, fuzzy
msgid "Buddy List Theme"
-msgstr "Danh sách bạn bè"
+msgstr "Sắc thái Danh sách Bạn chát"
#. System Tray
msgid "System Tray Icon"
@@ -12702,9 +12518,8 @@ msgstr "_Hiện biểu tượng trên khay:"
msgid "On unread messages"
msgstr "Khi có tin nhẳn chưa đọc"
-#, fuzzy
msgid "Conversation Window"
-msgstr "Cửa sổ cuộc thoại Tin Nhắn"
+msgstr "Cửa sổ nói chuyện"
msgid "_Hide new IM conversations:"
msgstr "Ẩn cuộc t_hoại Tin Nhắn mới:"
@@ -12804,16 +12619,15 @@ msgstr "Không thể khởi chạy chương trình cấu hình máy phục vụ
msgid "Cannot start browser configuration program."
msgstr "Không thể khởi chạy chương trình cấu hình trình duyệt."
-#, fuzzy
msgid "Disabled"
-msgstr "_Tắt"
+msgstr "Bị tắt"
-#, fuzzy, c-format
+#, c-format
msgid "Use _automatically detected IP address: %s"
-msgstr "Tự động tìm r_a địa chỉ IP"
+msgstr "Dùng đị_a chỉ IP tự động phát hiện: %s"
msgid "<span style=\"italic\">Example: stunserver.org</span>"
-msgstr "<span style=\"italic\">Thí dụ : stunserver.org</span>"
+msgstr "<span style=\"italic\">Ví dụ : stunserver.org</span>"
msgid "Public _IP:"
msgstr "_IP công:"
@@ -12835,11 +12649,10 @@ msgstr "Cổng cuố_i:"
#. TURN server
msgid "Relay Server (TURN)"
-msgstr ""
+msgstr "Máy phục vụ tiếp lại (TURN)"
-#, fuzzy
msgid "_TURN server:"
-msgstr "Máy phục vụ ST_UN:"
+msgstr "Máy phục vụ _TURN:"
msgid "Proxy Server &amp; Browser"
msgstr "Máy phục vụ ủy nhiệm và Trình duyệt"
@@ -12871,7 +12684,7 @@ msgstr "Không ủy nhiệm"
#. This is a global option that affects SOCKS4 usage even with account-specific proxy settings
msgid "Use remote DNS with SOCKS4 proxies"
-msgstr ""
+msgstr "Dùng DNS từ xa với ủy nhiệm SOCKS4"
msgid "_User:"
msgstr "_Người dùng:"
@@ -12897,10 +12710,10 @@ msgid "Konqueror"
msgstr "Konqueror"
msgid "Desktop Default"
-msgstr "Màn hình nền mặc định"
+msgstr "Mặc định môi trường"
msgid "GNOME Default"
-msgstr "GNOME mặc định"
+msgstr "Mặc định GNOME"
# Tên trình duyệt Web
msgid "Galeon"
@@ -12928,7 +12741,7 @@ msgid "_Browser:"
msgstr "Trình _duyệt:"
msgid "_Open link in:"
-msgstr "_Mở liên kết trong:"
+msgstr "_Mở liên kết bằng:"
msgid "Browser default"
msgstr "Trình duyệt mặc định"
@@ -13003,20 +12816,17 @@ msgstr ""
"_Lệnh âm thanh:\n"
"(%s cho tên tập tin)"
-#, fuzzy
msgid "M_ute sounds"
-msgstr "Câm âm _thanh"
+msgstr "Câm âm th_anh"
msgid "Sounds when conversation has _focus"
msgstr "Âm thanh khi cuộc thoại có tiê_u điểm"
-#, fuzzy
msgid "_Enable sounds:"
-msgstr "Bật âm thanh:"
+msgstr "_Bật âm thanh:"
-#, fuzzy
msgid "V_olume:"
-msgstr "Âm lượng:"
+msgstr "Â_m lượng:"
msgid "Play"
msgstr "Chơi"
@@ -13034,7 +12844,7 @@ msgid "Based on keyboard or mouse use"
msgstr "Dựa vào cách sử dụng bàn phím hay con chuột"
msgid "_Auto-reply:"
-msgstr "T_rả lời tự động:"
+msgstr "_Tự động đáp ứng:"
msgid "When both away and idle"
msgstr "Khi cả hai vắng mặt và nghỉ"
@@ -13196,14 +13006,13 @@ msgstr "Lưu _và Dùng"
msgid "Status for %s"
msgstr "Trạng thái cho %s"
-#, fuzzy, c-format
+#, c-format
msgid ""
"A custom smiley for '%s' already exists. Please use a different shortcut."
-msgstr ""
-"Đã có một hình cười tự chọn cho lối tắt đã chọn. Hãy ghi rõ một lối tắt khác."
+msgstr "Đã có một hình cười tự chọn cho « %s ». Hãy dùng một lối tắt khác."
msgid "Custom Smiley"
-msgstr "Hình cười tự chọn"
+msgstr "Hình Cười Riêng"
msgid "Duplicate Shortcut"
msgstr "Nhân đôi lối tắt"
@@ -13214,36 +13023,30 @@ msgstr "Sửa hình cười"
msgid "Add Smiley"
msgstr "Thêm hình cười"
-#, fuzzy
msgid "_Image:"
-msgstr "Ả_nh"
+msgstr "Ả_nh:"
#. Shortcut text
-#, fuzzy
msgid "S_hortcut text:"
-msgstr "Lối tắt"
+msgstr "C_huỗi lối tắt:"
msgid "Smiley"
msgstr "Hình cười"
-#, fuzzy
msgid "Shortcut Text"
-msgstr "Lối tắt"
+msgstr "Chuỗi lối tắt"
msgid "Custom Smiley Manager"
-msgstr "Bộ Quản lý Hình cười Tự chọn"
+msgstr "Bộ Quản lý Hình cười Riêng"
-#, fuzzy
msgid "Select Buddy Icon"
-msgstr "Chọn bạn chát"
+msgstr "Chọn biểu tượng bạn chát"
-#, fuzzy
msgid "Click to change your buddyicon for this account."
-msgstr "Dùng biểu tượng bạn chát cho tài khoản này:"
+msgstr "Nhấn vào để thay đổi biểu tượng bạn chát cho tài khoản này."
-#, fuzzy
msgid "Click to change your buddyicon for all accounts."
-msgstr "Dùng biểu tượng bạn chát cho tài khoản này:"
+msgstr "Nhấn vào để thay đổi biểu tượng bạn chát cho tất cả các tài khoản."
msgid "Waiting for network connection"
msgstr "Đợi kết nối đến mạng"
@@ -13323,13 +13126,12 @@ msgstr ""
msgid "Cannot send launcher"
msgstr "Không thể gửi bộ khởi chạy"
-#, fuzzy
msgid ""
"You dragged a desktop launcher. Most likely you wanted to send the target of "
"this launcher instead of this launcher itself."
msgstr ""
"Bạn đã kéo một bộ khởi chạy của môi trường. Rất có thể là bạn muốn gửi đích "
-"của bộ khởi chạy, hơn là bộ khởi chạy chính nó."
+"đến của bộ khởi chạy, hơn là bộ khởi chạy chính nó."
#, c-format
msgid ""
@@ -13339,7 +13141,7 @@ msgid ""
msgstr ""
"<b>Tập tin:</b> %s\n"
"<b>Kích cỡ tập tin:</b> %s\n"
-"<b>Kích ỡ ảnh:</b> %dx%d"
+"<b>Kích cỡ ảnh:</b> %dx%d"
#, c-format
msgid "The file '%s' is too large for %s. Please try a smaller image.\n"
@@ -13361,9 +13163,8 @@ msgid ""
msgstr ""
"Không nạp được ảnh « %s »: không biết sao, rất có thể là tập tin ảnh bị hỏng"
-#, fuzzy
msgid "_Open Link"
-msgstr "_Mở liên kết trong:"
+msgstr "_Mở liên kết"
msgid "_Copy Link Location"
msgstr "_Chép địa chỉ liên kết"
@@ -13371,24 +13172,20 @@ msgstr "_Chép địa chỉ liên kết"
msgid "_Copy Email Address"
msgstr "_Chép địa chỉ thư"
-#, fuzzy
msgid "_Open File"
-msgstr "Mở tập tin..."
+msgstr "_Mở tập tin"
-#, fuzzy
msgid "Open _Containing Directory"
-msgstr "Thư mục chứa sổ theo dõi"
+msgstr "Mở thư mục _chứa"
msgid "Save File"
msgstr "Lưu tập tin"
-#, fuzzy
msgid "_Play Sound"
-msgstr "Chơi âm thanh"
+msgstr "_Phát âm thanh"
-#, fuzzy
msgid "_Save File"
-msgstr "Lưu tập tin"
+msgstr "_Lưu tập tin"
msgid "Select color"
msgstr "Chọn màu"
@@ -13405,20 +13202,17 @@ msgstr "_Lấy thông tin"
msgid "_Invite"
msgstr "Mờ_i"
-#, fuzzy
msgid "_Modify..."
-msgstr "_Sửa"
+msgstr "_Sửa..."
-#, fuzzy
msgid "_Add..."
-msgstr "Thê_m"
+msgstr "Thê_m..."
msgid "_Open Mail"
msgstr "_Mở thư"
-#, fuzzy
msgid "_Edit"
-msgstr "Sửa"
+msgstr "_Sửa"
msgid "Pidgin Tooltip"
msgstr "Gợi ý Công cụ Pidgin"
@@ -13436,12 +13230,11 @@ msgstr "Chọn điều này thì tắt dùng biểu tượng xúc cảm kiểu
msgid "none"
msgstr "không có"
-#, fuzzy
msgid "Small"
-msgstr "Địa chỉ thư"
+msgstr "Nhỏ"
msgid "Smaller versions of the default smilies"
-msgstr ""
+msgstr "Phiên bản nhỏ của các hình cười mặc định"
msgid "Response Probability:"
msgstr "Xác suất đáp ứng:"
@@ -13576,78 +13369,65 @@ msgstr "Áp dụng trong Tin Nhắn"
#. 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
-#, fuzzy
msgid "Server name request"
-msgstr "Địa chỉ máy phục vụ"
+msgstr "Yêu cầu tên máy phục vụ"
-#, fuzzy
msgid "Enter an XMPP Server"
-msgstr "Nhập máy phục vụ hội thảo"
+msgstr "Nhập một máy phục vụ XMPP"
-#, fuzzy
msgid "Select an XMPP server to query"
-msgstr "Chọn một máy phục vụ hội thảo để hỏi"
+msgstr "Chọn một máy phục vụ XMPP để hỏi"
-#, fuzzy
msgid "Find Services"
-msgstr "Dịch vụ trực tuyến"
+msgstr "Tìm dịch vụ"
-#, fuzzy
msgid "Add to Buddy List"
-msgstr "Gửi danh sách bạn bè"
+msgstr "Thêm vào danh sách bạn chát"
-#, fuzzy
msgid "Gateway"
-msgstr "Đi vắng"
+msgstr "Cổng ra"
-#, fuzzy
msgid "Directory"
-msgstr "Thư mục chứa sổ theo dõi"
+msgstr "Thư mục"
-#, fuzzy
msgid "PubSub Collection"
-msgstr "Chọn âm thanh"
+msgstr "Tập hợp PubSub"
-#, fuzzy
msgid "PubSub Leaf"
-msgstr "Dịch vụ PubSub"
+msgstr "PubSub Leaf"
-#, fuzzy
msgid ""
"\n"
"<b>Description:</b> "
-msgstr "Mô tả"
+msgstr ""
+"\n"
+"<b>Mô tả:</b> "
#. Create the window.
-#, fuzzy
msgid "Service Discovery"
-msgstr "Thông tin phát hiện dịch vụ"
+msgstr "Phát hiện Dịch vụ"
-#, fuzzy
msgid "_Browse"
-msgstr "Trình _duyệt:"
+msgstr "_Duyệt"
-#, fuzzy
msgid "Server does not exist"
-msgstr "Người dùng đó không tồn tại."
+msgstr "Máy phục vụ không tồn tại"
-#, fuzzy
msgid "Server does not support service discovery"
-msgstr "Máy phục vụ không sử dụng bất kỳ phương thức xác thực được hỗ trợ nào"
+msgstr "Máy phục vụ không hỗ trợ chức năng phát hiện dịch vụ"
-#, fuzzy
msgid "XMPP Service Discovery"
-msgstr "Thông tin phát hiện dịch vụ"
+msgstr "Phát hiện Dịch vụ XMPP"
msgid "Allows browsing and registering services."
-msgstr ""
+msgstr "Cho phép duyệt qua và đăng ký các dịch vụ."
-#, fuzzy
msgid ""
"This plugin is useful for registering with legacy transports or other XMPP "
"services."
msgstr ""
-"Phần bổ sung này có ích để gỡ lỗi máy phục vụ hay trình khách kiểu XMPP."
+"Phần bổ sung này có ích để đăng ký với mạng truyền tải thừa tự, hay dịch vụ "
+"XMPP khác."
msgid "By conversation count"
msgstr "Theo số đếm cuộc thoại"
@@ -13946,7 +13726,6 @@ msgid "Music Messaging Plugin for collaborative composition."
msgstr "Phần bổ sung tin nhẳn nhạc để soạn nhạc một cách hợp tác."
#. * summary
-#, fuzzy
msgid ""
"The Music Messaging Plugin allows a number of users to simultaneously work "
"on a piece of music by editing a common score in real-time."
@@ -13983,9 +13762,8 @@ msgstr "Chèn số đếm tin nhẳn mới vào thuộc tính _X"
msgid "Set window manager \"_URGENT\" hint"
msgstr "Lập ẩn ý « _Khẩn » của bộ quản lý cửa sổ"
-#, fuzzy
msgid "_Flash window"
-msgstr "Cửa sổ C_hat"
+msgstr "Nhá_y cửa sổ"
#. Raise window method button
msgid "R_aise conversation window"
@@ -14066,14 +13844,12 @@ msgstr ""
msgid "Hyperlink Color"
msgstr "Màu siêu liên kết"
-#, fuzzy
msgid "Visited Hyperlink Color"
-msgstr "Màu siêu liên kết"
+msgstr "Màu siêu liên kết đã thăm"
msgid "Highlighted Message Name Color"
msgstr "Màu tên tin nhẳn tô sáng"
-#, fuzzy
msgid "Typing Notification Color"
msgstr "Màu thông báo đang gõ"
@@ -14106,23 +13882,20 @@ msgstr "Phông giao diện GTK+"
msgid "GTK+ Text Shortcut Theme"
msgstr "Sắc thái lối tắt văn bản GTK+"
-#, fuzzy
msgid "Disable Typing Notification Text"
-msgstr "Bật thông báo đang gõ"
+msgstr "Tắt chuỗi thông báo đang gõ"
-#, fuzzy
msgid "GTK+ Theme Control Settings"
-msgstr "Điều khiển sắc thái GTK+ Pidgin"
+msgstr "Thiết lập Điều khiển Sắc thái GTK+"
-#, fuzzy
msgid "Colors"
-msgstr "Đóng"
+msgstr "Màu sắc"
msgid "Fonts"
msgstr "Phông"
msgid "Miscellaneous"
-msgstr ""
+msgstr "Linh tinh"
msgid "Gtkrc File Tools"
msgstr "Công cụ tập tin Gtkrc"
@@ -14155,18 +13928,16 @@ msgstr ""
#, c-format
msgid "You can upgrade to %s %s today."
-msgstr ""
+msgstr "Hôm nay bạn có dịp nâng cấp lên %s %s."
msgid "New Version Available"
msgstr "Hiện đang có phiên bản mới"
-#, fuzzy
msgid "Later"
-msgstr "Ngày tháng"
+msgstr "Về sau"
-#, fuzzy
msgid "Download Now"
-msgstr "Người dùng trên %s: %s"
+msgstr "Tải về ngay"
#. *< type
#. *< ui_requirement
@@ -14208,12 +13979,11 @@ msgid "Conversation Window Send Button."
msgstr "Cái nút Gửi trong Cửa sổ Cuộc thoại."
#. *< summary
-#, fuzzy
msgid ""
"Adds a Send button to the entry area of the conversation window. Intended "
"for use when no physical keyboard is present."
msgstr ""
-"Thêm một cái nút Gửi vào vùng nhập của cửa sổ cuộc thoát. Dự định cho trường "
+"Thêm một cái nút Gửi vào vùng nhập của cửa sổ cuộc thoại. Dự định cho trường "
"hợp không có bàn phím vật lý."
msgid "Duplicate Correction"
@@ -14268,98 +14038,81 @@ msgid "Replaces text in outgoing messages according to user-defined rules."
msgstr ""
"Thay thế văn bản trong tin nhẳn gửi đi theo qui tắc người dùng định ra."
-#, fuzzy
msgid "Just logged in"
-msgstr "Chưa đăng nhập"
+msgstr "Mới đăng nhập"
-#, fuzzy
msgid "Just logged out"
-msgstr "Chưa đăng nhập"
+msgstr "Mới đăng xuất"
msgid ""
"Icon for Contact/\n"
"Icon for Unknown person"
msgstr ""
+"Biểu tượng cho Liên lạc\n"
+"Biểu tượng cho Người lạ"
-#, fuzzy
msgid "Icon for Chat"
-msgstr "Tham gia Chat"
+msgstr "Biểu tượng cho Chát"
-#, fuzzy
msgid "Ignored"
-msgstr "Lờ"
+msgstr "Bị lờ"
-#, fuzzy
msgid "Founder"
-msgstr "To hơn"
+msgstr "Người sáng lập"
-# Tên trình duyệt Web
#. A user in a chat room who has special privileges.
-#, fuzzy
msgid "Operator"
-msgstr "Opera"
+msgstr "Thao tác viên"
#. A half operator is someone who has a subset of the privileges
#. that an operator has.
msgid "Half Operator"
-msgstr ""
+msgstr "Nửa thao tác viên"
-#, fuzzy
msgid "Authorization dialog"
-msgstr "Cho phép"
+msgstr "Hộp thoại cho phép"
-#, fuzzy
msgid "Error dialog"
-msgstr "Lỗi "
+msgstr "Hộp thoại lỗi "
-#, fuzzy
msgid "Information dialog"
-msgstr "Thông tin"
+msgstr "Hộp thoại thông tin"
msgid "Mail dialog"
-msgstr ""
+msgstr "Hộp thoại thư tín"
-#, fuzzy
msgid "Question dialog"
-msgstr "Hộp thoại yêu cầu"
+msgstr "Hộp thoại câu hỏi"
-#, fuzzy
msgid "Warning dialog"
-msgstr "Mức cảnh báo"
+msgstr "Hộp thoại cảnh báo"
msgid "What kind of dialog is this?"
-msgstr ""
+msgstr "Hộp thoại này có loại nào?"
-#, fuzzy
msgid "Status Icons"
-msgstr "Trạng thái cho %s"
+msgstr "Biểu tượng Trạng thái"
-#, fuzzy
msgid "Chatroom Emblems"
-msgstr "Miền địa phương phòng chát"
+msgstr "Hình tượng phòng chát"
-#, fuzzy
msgid "Dialog Icons"
-msgstr "Lưu biểu tượng"
+msgstr "Biểu tượng Hộp thoại"
-#, fuzzy
msgid "Pidgin Icon Theme Editor"
-msgstr "Điều khiển sắc thái GTK+ Pidgin"
+msgstr "Bộ Sửa Sắc thái Biểu tượng Pidgin"
-#, fuzzy
msgid "Contact"
-msgstr "Thông tin liên lạc"
+msgstr "Liên lạc"
-#, fuzzy
msgid "Pidgin Buddylist Theme Editor"
-msgstr "Danh sách bạn bè"
+msgstr "Bộ Sửa Sắc thái Danh sách Bạn chát Pidgin"
-#, fuzzy
msgid "Edit Buddylist Theme"
-msgstr "Danh sách bạn bè"
+msgstr "Sửa sắc thái danh sách bạn chát"
msgid "Edit Icon Theme"
-msgstr ""
+msgstr "Sửa sắc thái biểu tượng"
#. *< type
#. *< ui_requirement
@@ -14368,16 +14121,14 @@ msgstr ""
#. *< priority
#. *< id
#. * description
-#, fuzzy
msgid "Pidgin Theme Editor"
-msgstr "Điều khiển sắc thái GTK+ Pidgin"
+msgstr "Bộ Sửa Sắc thái Pidgin"
#. *< name
#. *< version
#. * summary
-#, fuzzy
msgid "Pidgin Theme Editor."
-msgstr "Điều khiển sắc thái GTK+ Pidgin"
+msgstr "Bộ Sửa Sắc thái Pidgin."
#. *< type
#. *< ui_requirement
@@ -14462,35 +14213,29 @@ msgstr ""
"Phần bổ sung này cho phép người dùng tùy chỉnh các định dạng của nhãn thời "
"gian trong tin nhẳn cuộc thoại và bản ghi."
-#, fuzzy
msgid "Audio"
-msgstr "Tác giả"
+msgstr "Âm thanh"
-#, fuzzy
msgid "Video"
-msgstr "Ảnh động trực tiếp"
+msgstr "Phim"
msgid "Output"
-msgstr ""
+msgstr "Kết xuất"
-#, fuzzy
msgid "_Plugin"
-msgstr "Phần bổ sung"
+msgstr "_Phần bổ sung"
-#, fuzzy
msgid "_Device"
-msgstr "Thiết bị"
+msgstr "_Thiết bị"
msgid "Input"
-msgstr ""
+msgstr "Đầu vào"
-#, fuzzy
msgid "P_lugin"
-msgstr "Phần bổ sung"
+msgstr "Phần bổ _sung"
-#, fuzzy
msgid "D_evice"
-msgstr "Thiết bị"
+msgstr "Thiết _bị"
#. *< magic
#. *< major version
@@ -14501,18 +14246,19 @@ msgstr "Thiết bị"
#. *< dependencies
#. *< priority
#. *< id
-#, fuzzy
msgid "Voice/Video Settings"
-msgstr "Sửa thiết lập"
+msgstr "Thiết lập Thoại/Phim"
#. *< name
#. *< version
msgid "Configure your microphone and webcam."
-msgstr ""
+msgstr "Cấu hình máy vi âm và máy ảnh Web."
#. *< summary
msgid "Configure microphone and webcam settings for voice/video calls."
msgstr ""
+"Cấu hình thiết lập cái máy vi âm và cái máy ảnh Web để gọi với tiếng nói và/"
+"hay ảnh động."
msgid "Opacity:"
msgstr "Tính mờ đục:"
@@ -14570,9 +14316,6 @@ msgstr ""
"\n"
"Chú ý: phần bổ sung này yêu cầu bạn dùng Win2000 hoặc sau."
-msgid "GTK+ Runtime Version"
-msgstr "Phiên bản GTK+ Runtime"
-
#. Autostart
msgid "Startup"
msgstr "Khởi chạy"
@@ -14581,6 +14324,9 @@ msgstr "Khởi chạy"
msgid "_Start %s on Windows startup"
msgstr "_Chạy %s khi khởi động Windows"
+msgid "Allow multiple instances"
+msgstr "Cho phép nhiều thể hiện"
+
msgid "_Dockable Buddy List"
msgstr "_Danh sách bạn bè có thể neo lại"
@@ -14598,12 +14344,11 @@ msgstr "Tùy chọn Pidgin Windows"
msgid "Options specific to Pidgin for Windows."
msgstr "Tùy chọn riêng cho Pidgin trên Windows."
-#, fuzzy
msgid ""
"Provides options specific to Pidgin for Windows, such as buddy list docking."
msgstr ""
-"Cung cấo thiết lập đặc biệt cho Windows Pidgin, chẳng hạn như neo danh sách "
-"bạn bè."
+"Cung cấp các tuỳ chọn đặc biệt cho Windows Pidgin, chẳng hạn như neo danh "
+"sách bạn chát."
msgid "<font color='#777777'>Logged out.</font>"
msgstr "<font color='#777777'>Đã đăng xuất.</font>"
@@ -14642,959 +14387,3 @@ msgstr "Gửi và nhận các đoạn dòng XMPP thô."
msgid "This plugin is useful for debbuging XMPP servers or clients."
msgstr ""
"Phần bổ sung này có ích để gỡ lỗi máy phục vụ hay trình khách kiểu XMPP."
-
-#, fuzzy
-#~ msgid "Calling ... "
-#~ msgstr "Đang tính toán..."
-
-#~ msgid "Invalid certificate chain"
-#~ msgstr "Dãy chứng nhận không hợp lệ"
-
-#~ msgid ""
-#~ "The certificate chain presented by %s does not have a valid digital "
-#~ "signature from the Certificate Authority from which it claims to have a "
-#~ "signature."
-#~ msgstr ""
-#~ "%s đã cung cấp một dãy chứng nhận không có chữ ký số hợp lệ từ Nhà cầm "
-#~ "quyền chứng nhận từ đó nó tuyên bố có chữ ký."
-
-#~ msgid "Invalid certificate authority signature"
-#~ msgstr "Chữ ký nhà cầm quyền chứng nhận không hợp lệ"
-
-#~ msgid "Join/Part Hiding Configuration"
-#~ msgstr "Cấu hình ẩn việc Vào/Rời"
-
-#~ msgid "Minimum Room Size"
-#~ msgstr "Kích cỡ phòng tối thiểu"
-
-#~ msgid "User Inactivity Timeout (in minutes)"
-#~ msgstr "Thời hạn người dùng không hoạt động (theo phút)"
-
-#, fuzzy
-#~ msgid "Malformed BOSH Connect Server"
-#~ msgstr "Không kết nối được với máy phục vụ."
-
-#, fuzzy
-#~ msgid "Failed to open the file"
-#~ msgstr "Không mở được tập tin « %s »: %s"
-
-#, fuzzy
-#~ msgid "Unable to not load SILC key pair"
-#~ msgstr "Không thể nạp cặp khoá SILC"
-
-#~ msgid "Your account is locked, please log in to the Yahoo! website."
-#~ msgstr ""
-#~ "Tài khoản của bạn đã bị khóa, hãy đăng nhập vào địa chỉ Web của Yahoo."
-
-#~ msgid ""
-#~ "%s declined your conference invitation to room \"%s\" because \"%s\"."
-#~ msgstr "%s đã từ chối lời mời hội thảo ở phòng « %s » bởi vì « %s »."
-
-#~ msgid "Invitation Rejected"
-#~ msgstr "Lời mời không được chấp nhận"
-
-#, fuzzy
-#~ msgid "_Proxy"
-#~ msgstr "Ủy nhiệm"
-
-#~ msgid "Euskera(Basque)"
-#~ msgstr "Tiếng Ba-x-quợ"
-
-#~ msgid "_Resume"
-#~ msgstr "Tiếp tụ_c"
-
-#, fuzzy
-#~ msgid ""
-#~ "%s %s\n"
-#~ "Usage: %s [OPTION]...\n"
-#~ "\n"
-#~ " -c, --config=DIR use DIR for config files\n"
-#~ " -d, --debug print debugging messages to stdout\n"
-#~ " -f, --force-online force online, regardless of network status\n"
-#~ " -h, --help display this help and exit\n"
-#~ " -m, --multiple do not ensure single instance\n"
-#~ " -n, --nologin don't automatically login\n"
-#~ " -l, --login[=NAME] 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).\n"
-#~ " --display=DISPLAY X display to use\n"
-#~ " -v, --version display the current version and exit\n"
-#~ msgstr ""
-#~ "%s %s\n"
-#~ "Sử dụng: %s [TÙY_CHỌN]...\n"
-#~ "\n"
-#~ " -c, --config=THƯ_MỤC giữ các tập tin cấu hình trong thư mục này\n"
-#~ " -d, --debug \tin các thông điệp gỡ lỗi ra đầu ra tiêu chuẩn\n"
-#~ " -h, --help \t\thiển thị trợ giúp này rồi thoát\n"
-#~ " -m, --multiple \tkhông đảm bảo chỉ một thể hiện\n"
-#~ " -n, --nologin \tđừng tự động đăng nhập\n"
-#~ " -l, --login[=TÊN] \tbật (những) tài khoản đã ghi rõ (đối số tùy chọn "
-#~ "TÊN\n"
-#~ "\t\tcũng ghi rõ (những) tài khoản cần dùng, định giới bằng dấu phẩy.\n"
-#~ "\t\tKhông đưa ra thì chỉ bật tài khoản thứ nhất.)\n"
-#~ " --display=BỘ_TRÌNH_BÀY\t\tbộ trình bày X cần dùng\n"
-#~ " -v, --version \t\thiển thị phiên bản hiện thời rồi thoát\n"
-
-#, fuzzy
-#~ msgid ""
-#~ "%s %s\n"
-#~ "Usage: %s [OPTION]...\n"
-#~ "\n"
-#~ " -c, --config=DIR use DIR for config files\n"
-#~ " -d, --debug print debugging messages to stdout\n"
-#~ " -f, --force-online force online, regardless of network status\n"
-#~ " -h, --help display this help and exit\n"
-#~ " -m, --multiple do not ensure single instance\n"
-#~ " -n, --nologin don't automatically login\n"
-#~ " -l, --login[=NAME] 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).\n"
-#~ " -v, --version display the current version and exit\n"
-#~ msgstr ""
-#~ "%s %s\n"
-#~ "Sử dụng: %s [TÙY_CHỌN]...\n"
-#~ "\n"
-#~ " -c, --config=THƯ_MỤC giữ các tập tin cấu hình trong thư mục này\n"
-#~ " -d, --debug \tin các thông điệp gỡ lỗi ra đầu ra tiêu chuẩn\n"
-#~ " -h, --help \t\thiển thị trợ giúp này rồi thoát\n"
-#~ " -m, --multiple \tkhông đảm bảo chỉ một thể hiện\n"
-#~ " -n, --nologin \tđừng tự động đăng nhập\n"
-#~ " -l, --login[=TÊN] \tbật (những) tài khoản đã ghi rõ (đối số tùy chọn "
-#~ "TÊN\n"
-#~ "\t\tcũng ghi rõ (những) tài khoản cần dùng, định giới bằng dấu phẩy.\n"
-#~ "\t\tKhông đưa ra thì chỉ bật tài khoản thứ nhất.)\n"
-#~ " -v, --version \t\thiển thị phiên bản hiện thời rồi thoát\n"
-
-#~ msgid "Cannot open socket"
-#~ msgstr "Không thể mở ổ cắm."
-
-#~ msgid "Could not listen on socket"
-#~ msgstr "Không thể lắng nghe trên ổ cắm"
-
-#~ msgid "Unable to read socket"
-#~ msgstr "Không thể đọc ổ cắm"
-
-#~ msgid "Connection failed."
-#~ msgstr "Lỗi kết nối."
-
-#~ msgid "Server has disconnected"
-#~ msgstr "Máy phục vụ đã ngắt kết nối"
-
-#~ msgid "Couldn't create socket"
-#~ msgstr "Không thể tạo ổ cắm"
-
-#~ msgid "Couldn't connect to host"
-#~ msgstr "Không thể kết nối với máy phục vụ"
-
-#~ msgid "Read error"
-#~ msgstr "Lỗi đọc"
-
-#~ msgid ""
-#~ "Could not establish a connection with the server:\n"
-#~ "%s"
-#~ msgstr ""
-#~ "Không thể thiết lập kết nối đến máy phục vụ :\n"
-#~ "%s"
-
-#~ msgid "Write error"
-#~ msgstr "Lỗi ghi"
-
-#~ msgid "Last Activity"
-#~ msgstr "Hoạt động cuối cùng"
-
-#~ msgid "Service Discovery Info"
-#~ msgstr "Thông tin phát hiện dịch vụ"
-
-#~ msgid "Service Discovery Items"
-#~ msgstr "Mục phát hiện dịch vụ"
-
-#~ msgid "Extended Stanza Addressing"
-#~ msgstr "Đặt địa chỉ kiểu đoạn dòng mở rộng"
-
-#~ msgid "Multi-User Chat"
-#~ msgstr "Chat đa người dùng"
-
-#~ msgid "Multi-User Chat Extended Presence Information"
-#~ msgstr "Thông tin về mặt ở đã mở rộng cho chat đa người dùng"
-
-#~ msgid "In-Band Bytestreams"
-#~ msgstr "Luồng byte bên trong dải"
-
-#~ msgid "Ad-Hoc Commands"
-#~ msgstr "Lệnh như thế"
-
-#~ msgid "PubSub Service"
-#~ msgstr "Dịch vụ PubSub"
-
-#~ msgid "SOCKS5 Bytestreams"
-#~ msgstr "Luồng byte SOCKS5"
-
-#~ msgid "Out of Band Data"
-#~ msgstr "Dữ liệu bên ngoài dải"
-
-#~ msgid "XHTML-IM"
-#~ msgstr "XHTML-IM"
-
-#~ msgid "In-Band Registration"
-#~ msgstr "Đăng ký bên trong dải"
-
-#~ msgid "User Location"
-#~ msgstr "Nơi ở người dùng"
-
-#~ msgid "User Avatar"
-#~ msgstr "Ảnh riêng người dùng"
-
-#~ msgid "Chat State Notifications"
-#~ msgstr "Thông báo tình trạng chat"
-
-#~ msgid "Software Version"
-#~ msgstr "Phiên bản phần mềm"
-
-#~ msgid "Stream Initiation"
-#~ msgstr "Khởi tạo luồng"
-
-#~ msgid "User Mood"
-#~ msgstr "Tâm trạng người dùng"
-
-#~ msgid "User Activity"
-#~ msgstr "Hoạt động người dùng"
-
-#~ msgid "Entity Capabilities"
-#~ msgstr "Khả năng thực thể"
-
-#~ msgid "Encrypted Session Negotiations"
-#~ msgstr "Dàn xếp phiên bản đã mật mã"
-
-#~ msgid "User Tune"
-#~ msgstr "Điệu người dùng"
-
-#~ msgid "Roster Item Exchange"
-#~ msgstr "Trao đổi mục bản liệt kê"
-
-#~ msgid "Reachability Address"
-#~ msgstr "Địa chỉ có thể tới"
-
-#~ msgid "User Profile"
-#~ msgstr "Lý lịch người dùng"
-
-# Name: don't translate/Tên: đừng dịch
-#~ msgid "Jingle"
-#~ msgstr "Jingle"
-
-#~ msgid "Jingle Audio"
-#~ msgstr "Âm thanh Jingle"
-
-#~ msgid "User Nickname"
-#~ msgstr "Tên hiệu người dùng"
-
-#~ msgid "Jingle ICE UDP"
-#~ msgstr "Jingle ICE UDP"
-
-#~ msgid "Jingle ICE TCP"
-#~ msgstr "Jingle ICE TCP"
-
-#~ msgid "Jingle Raw UDP"
-#~ msgstr "Jingle Raw UDP"
-
-#~ msgid "Jingle Video"
-#~ msgstr "Ảnh động Jingle"
-
-#~ msgid "Jingle DTMF"
-#~ msgstr "Jingle DTMF"
-
-#~ msgid "Message Receipts"
-#~ msgstr "Người nhận tin nhẳn"
-
-#~ msgid "Public Key Publishing"
-#~ msgstr "Xuất bản khoá công"
-
-#~ msgid "User Chatting"
-#~ msgstr "Người dùng nói chuyện"
-
-#~ msgid "User Browsing"
-#~ msgstr "Người dùng duyệt"
-
-#~ msgid "User Gaming"
-#~ msgstr "Người dùng chơi trò"
-
-#~ msgid "User Viewing"
-#~ msgstr "Người dùng xem"
-
-#~ msgid "Stanza Encryption"
-#~ msgstr "Mật mã đoạn dòng"
-
-#~ msgid "Entity Time"
-#~ msgstr "Thời gian thực thể"
-
-#~ msgid "Delayed Delivery"
-#~ msgstr "Phát trễ"
-
-#~ msgid "Collaborative Data Objects"
-#~ msgstr "Đối tượng dữ liệu hợp tác"
-
-#~ msgid "File Repository and Sharing"
-#~ msgstr "Kho lưu tập tin và chia sẻ"
-
-#~ msgid "STUN Service Discovery for Jingle"
-#~ msgstr "Phát hiện dịch vụ STUN cho Jingle"
-
-#~ msgid "Simplified Encrypted Session Negotiation"
-#~ msgstr "Dàn xếp phiên chạy mật mã đơn giản"
-
-#~ msgid "Hop Check"
-#~ msgstr "Kiểm tra bước"
-
-#~ msgid "Read Error"
-#~ msgstr "Lỗi đọc"
-
-#~ msgid "Failed to connect to server."
-#~ msgstr "Không kết nối được với máy phục vụ."
-
-#~ msgid "Read buffer full (2)"
-#~ msgstr "Bộ đệm đọc đã đầy (2)"
-
-#~ msgid "Unparseable message"
-#~ msgstr "Thông điệp không thể phân tích"
-
-#~ msgid "Couldn't connect to host: %s (%d)"
-#~ msgstr "Không thể kết nối đến máy: %s (%d)"
-
-#~ msgid "Login failed (%s)."
-#~ msgstr "Không đăng nhập được (%s)"
-
-#~ msgid ""
-#~ "You have been logged out because you logged in at another workstation."
-#~ msgstr "Bạn bị đăng xuất vì bạn cũng đăng nhập bằng một máy trạm khác."
-
-#~ msgid "Error. SSL support is not installed."
-#~ msgstr "Lỗi: chưa cài đặt khả năng hỗ trợ SSL."
-
-#~ msgid "Incorrect password."
-#~ msgstr "Mật khẩu sai."
-
-#~ msgid ""
-#~ "Could not connect to BOS server:\n"
-#~ "%s"
-#~ msgstr ""
-#~ "Không thể kết nối tới máy phục vụ BOS:\n"
-#~ "%s"
-
-#~ msgid "You may be disconnected shortly. Check %s for updates."
-#~ msgstr ""
-#~ "Bạn có thể bị ngắt kết nối một thời gian ngắn. Hãy kiểm tra %s để cập "
-#~ "nhật."
-
-#~ msgid "Could Not Connect"
-#~ msgstr "Không thể kết nối"
-
-#~ msgid "Invalid username."
-#~ msgstr "Tên người dùng sai."
-
-#, fuzzy
-#~ msgid "Could not decrypt server reply"
-#~ msgstr "Không thể lấy thông tin về máy phục vụ"
-
-#~ msgid "Connection lost"
-#~ msgstr "Kết nối bị mất"
-
-#~ msgid "Couldn't resolve host"
-#~ msgstr "Không thể giải quyết máy"
-
-#~ msgid "Connection closed (writing)"
-#~ msgstr "Kết nối bị đóng (đang ghi)"
-
-#~ msgid "Connection reset"
-#~ msgstr "Kết nối bị đặt lại"
-
-#~ msgid "Error reading from socket: %s"
-#~ msgstr "Lỗi đọc từ ổ cắm: %s"
-
-#~ msgid "Unable to connect to host"
-#~ msgstr "Không thể kết nối đến máy"
-
-#~ msgid "Could not write"
-#~ msgstr "Không thể ghi"
-
-#~ msgid "Could not connect"
-#~ msgstr "Không thể kết nối"
-
-#~ msgid "Could not create listen socket"
-#~ msgstr "Không thể tạo ổ cắm lắng nghe"
-
-#~ msgid "Could not resolve hostname"
-#~ msgstr "Không thể giải quyết tên máy"
-
-#, fuzzy
-#~ msgid "Incorrect Password"
-#~ msgstr "Mật khẩu sai"
-
-#~ msgid ""
-#~ "Could not establish a connection with %s:\n"
-#~ "%s"
-#~ msgstr ""
-#~ "Không thể thiết lập kết nối với %s:\n"
-#~ "%s"
-
-#~ msgid "Yahoo Japan"
-#~ msgstr "Yahoo Nhật bản"
-
-#~ msgid "Japan Pager server"
-#~ msgstr "Máy phục vụ nhắn tin Nhật bản"
-
-#~ msgid "Japan file transfer server"
-#~ msgstr "Máy phục vụ truyền tập tin Nhật bản"
-
-#~ msgid ""
-#~ "Lost connection with server\n"
-#~ "%s"
-#~ msgstr ""
-#~ "Mất kết nối với máy phục vụ\n"
-#~ "%s"
-
-#~ msgid "Could not resolve host name"
-#~ msgstr "Không thể giải quyết tên máy."
-
-#, fuzzy
-#~ msgid ""
-#~ "Unable to connect to %s: Server requires TLS/SSL, but no TLS/SSL support "
-#~ "was found."
-#~ msgstr ""
-#~ "Máy phục vụ yêu cầu TLS/SSL để đăng nhập. Không tìm thấy khả năng hỗ trợ "
-#~ "TLS/SSL."
-
-#~ msgid "Conversation Window Hiding"
-#~ msgstr "Ẩn cửa sổ cuộc thoại"
-
-#~ msgid "More Data needed"
-#~ msgstr "Cần thêm dữ liệu"
-
-#~ msgid "Please provide a shortcut to associate with the smiley."
-#~ msgstr "Hãy cung cấp một lối tắt cần liên quan đến hình cười đó."
-
-#~ msgid "Please select an image for the smiley."
-#~ msgstr "Hãy chọn một ảnh cho hình cười đó."
-
-#~ msgid "Activate which ID?"
-#~ msgstr "Kích hoạt ID nào ?"
-
-#~ msgid "Cursor Color"
-#~ msgstr "Màu con trỏ"
-
-#~ msgid "Secondary Cursor Color"
-#~ msgstr "Màu con trỏ phụ"
-
-#~ msgid "Interface colors"
-#~ msgstr "Màu sắc giao diện"
-
-#~ msgid "Widget Sizes"
-#~ msgstr "Kích cỡ ô điều khiển"
-
-#~ msgid "Invite message"
-#~ msgstr "Lời mời"
-
-#~ msgid ""
-#~ "Please enter the name of the user you wish to invite,\n"
-#~ "along with an optional invite message."
-#~ msgstr ""
-#~ "Hãy nhập tên người dùng mà bạn muốn mời,\n"
-#~ "kèm theo lời mời tùy ý."
-
-#~ msgid "Unable to retrieve MSN Address Book"
-#~ msgstr "Không thể lấy Sổ địa chỉ MSN"
-
-#~ msgid "Connection to server lost (no data received within %d second)"
-#~ msgid_plural ""
-#~ "Connection to server lost (no data received within %d seconds)"
-#~ msgstr[0] ""
-#~ "Kết nối đến máy phục vụ bị mất (không nhận dữ liệu trong vòng %d giây)"
-
-#~ msgid ""
-#~ "You may be disconnected shortly. You may want to use TOC until this is "
-#~ "fixed. Check %s for updates."
-#~ msgstr ""
-#~ "Bạn có thể bị ngắt kết nối một thời gian ngắn. Trong lúc chờ đợi lỗi được "
-#~ "sửa, bạn có thể sử dụng TOC. Hãy kiểm tra %s để cập nhật."
-
-#, fuzzy
-#~ msgid "Add buddy Q&A"
-#~ msgstr "Thêm bạn thân"
-
-#, fuzzy
-#~ msgid "Can not decrypt get server reply"
-#~ msgstr "Không thể lấy thông tin về máy phục vụ"
-
-#~ msgid "Keep alive error"
-#~ msgstr "Lỗi giữ cho kết nối hoạt động"
-
-#, fuzzy
-#~ msgid ""
-#~ "Lost connection with server:\n"
-#~ "%d, %s"
-#~ msgstr ""
-#~ "Mất kết nối với máy phục vụ :\n"
-#~ "%s"
-
-#, fuzzy
-#~ msgid "Connecting server ..."
-#~ msgstr "Máy phục vụ kết nối"
-
-#~ msgid "Failed to send IM."
-#~ msgstr "Không gửi được tin nhắn."
-
-#, fuzzy
-#~ msgid "Not a member of room \"%s\"\n"
-#~ msgstr "Bạn [%d] đã được thêm vào nhóm « %d »"
-
-#~ msgid "Looking up %s"
-#~ msgstr "Đang tra tìm %s"
-
-#~ msgid "Connect to %s failed"
-#~ msgstr "Kết nối đến %s không được"
-
-#~ msgid "Signon: %s"
-#~ msgstr "Đăng nhập: %s"
-
-#~ msgid "Unable to write file %s."
-#~ msgstr "Không thể ghi tập tin %s."
-
-#~ msgid "Unable to read file %s."
-#~ msgstr "Không thể đọc tập tin %s."
-
-#~ msgid "Message too long, last %s bytes truncated."
-#~ msgstr "Tin nhắn quá dài, %s byte cuối bị cắt ngắn."
-
-#~ msgid "%s not currently logged in."
-#~ msgstr "%s chưa đăng nhập."
-
-#~ msgid "Warning of %s not allowed."
-#~ msgstr "Không cho phép cảnh báo cho %s."
-
-#~ msgid ""
-#~ "A message has been dropped, you are exceeding the server speed limit."
-#~ msgstr ""
-#~ "Một tin nhẳn không gửi đi được, bạn đang vượt quá tốc độ cho phép của máy "
-#~ "phục vụ."
-
-#~ msgid "Chat in %s is not available."
-#~ msgstr "Không có sẵn chát trong %s."
-
-#~ msgid "You are sending messages too fast to %s."
-#~ msgstr "Bạn đang gửi tin nhẳn quá nhanh đến %s."
-
-#~ msgid "You missed an IM from %s because it was too big."
-#~ msgstr "Bạn không nhận được tin nhắn từ %s vì nó quá lớn."
-
-#~ msgid "You missed an IM from %s because it was sent too fast."
-#~ msgstr "Bạn không nhận được tin nhắn từ %s vì nó được gửi quá nhanh."
-
-#~ msgid "Failure."
-#~ msgstr "Lỗi."
-
-#~ msgid "Too many matches."
-#~ msgstr "Quá nhiều kết quả trùng khớp."
-
-#~ msgid "Need more qualifiers."
-#~ msgstr "Cần thêm từ hạn định"
-
-#~ msgid "Dir service temporarily unavailable."
-#~ msgstr "Tạm thời không có dịch vụ danh bạ."
-
-#~ msgid "Email lookup restricted."
-#~ msgstr "Khả năng tra tìm địa chỉ thư điện tử bị hạn chế."
-
-#~ msgid "Keyword ignored."
-#~ msgstr "Từ khóa bị lờ đi."
-
-#~ msgid "No keywords."
-#~ msgstr "Không có từ khóa."
-
-#~ msgid "User has no directory information."
-#~ msgstr "Người dùng không có thông tin danh bạ."
-
-#~ msgid "Country not supported."
-#~ msgstr "Quốc gia chưa được hỗ trợ."
-
-#~ msgid "Failure unknown: %s."
-#~ msgstr "Lỗi không rõ : %s."
-
-#~ msgid "Incorrect username or password."
-#~ msgstr "Tên người dùng hay mật khẩu không đúng."
-
-#~ msgid "The service is temporarily unavailable."
-#~ msgstr "Tạm thời không có dịch vụ."
-
-#~ msgid "Your warning level is currently too high to log in."
-#~ msgstr "Mức cảnh báo của bạn hiện thời quá cao nên không đăng nhập được."
-
-#~ msgid ""
-#~ "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."
-#~ msgstr ""
-#~ "Bạn đã liên tục kết nối và ngắt kết nối quá nhiều. Hẵy đợi 10 phút và kết "
-#~ "nối lại. Nếu bạn vẫn cố kết nối, bạn sẽ phải chờ lâu hơn."
-
-#~ msgid "An unknown error, %d, has occurred. Info: %s"
-#~ msgstr ""
-#~ "Lỗi không rõ : %d.\n"
-#~ "Thông tin: %s"
-
-#~ msgid "Invalid Groupname"
-#~ msgstr "Tên nhóm không hợp lệ"
-
-#~ msgid "Connection Closed"
-#~ msgstr "Kết nối bị đóng"
-
-#~ msgid "Waiting for reply..."
-#~ msgstr "Đợi hồi âm..."
-
-#~ msgid "TOC has come back from its pause. You may now send messages again."
-#~ msgstr ""
-#~ "TOC thôi trạng thái tạm ngừng. Bây giờ bạn có thể gửi tin nhẳn trở lại."
-
-#~ msgid "Password Change Successful"
-#~ msgstr "Đổi mật khẩu thành công"
-
-#~ msgid "Get Dir Info"
-#~ msgstr "Lấy thông tin danh bạ"
-
-#~ msgid "Set Dir Info"
-#~ msgstr "Lập thông tin danh bạ"
-
-#~ msgid "Could not open %s for writing!"
-#~ msgstr "Không thể mở %s để ghi !"
-
-#~ msgid "File transfer failed; other side probably canceled."
-#~ msgstr "Không truyền được tập tin; bên khác có thể đã hủy bỏ."
-
-#~ msgid "Could not connect for transfer."
-#~ msgstr "Không thể kết nối để truyền đi."
-
-#~ msgid "Could not write file header. The file will not be transferred."
-#~ msgstr "Không thể ghi phần đầu tập tin. Tập tin sẽ không được truyền."
-
-#~ msgid "Save As..."
-#~ msgstr "Lưu dạng..."
-
-#~ msgid "%s requests %s to accept %d file: %s (%.2f %s)%s%s"
-#~ msgid_plural "%s requests %s to accept %d files: %s (%.2f %s)%s%s"
-#~ msgstr[0] "%s yêu cầu %s chấp nhận %d tập tin: %s (%.2f %s)%s%s"
-
-#~ msgid "%s requests you to send them a file"
-#~ msgstr "%s yêu cầu bạn gửi tập tin"
-
-#~ msgid "TOC Protocol Plugin"
-#~ msgstr "Phần bổ sung giao thức TOC"
-
-#~ msgid "User information for %s unavailable"
-#~ msgstr "Hiện không có thông tin người dùng về %s"
-
-#~ msgid "%s Options"
-#~ msgstr "%s Tùy chọn"
-
-#~ msgid "Proxy Options"
-#~ msgstr "Tùy chọn ủy nhiệm"
-
-#~ msgid "By log size"
-#~ msgstr "Theo kích cỡ bản ghi"
-
-#~ msgid "_Open Link in Browser"
-#~ msgstr "_Mở liên kết trong trình duyệt"
-
-#~ msgid "Smiley _Image"
-#~ msgstr "Ảnh cườ_i"
-
-#~ msgid "Smiley S_hortcut"
-#~ msgstr "Lối tắt _hình cười"
-
-#~ msgid "_Flash window when chat messages are received"
-#~ msgstr "Nhấp nhá_y cửa sổ khi nhận tin nhắn"
-
-#~ msgid "A group with the name already exists."
-#~ msgstr "Một nhóm tên đó đã có."
-
-#~ msgid "Primary Information"
-#~ msgstr "Thông tin chính"
-
-#~ msgid "Blood Type"
-#~ msgstr "Loại máu"
-
-#, fuzzy
-#~ msgid "Update information"
-#~ msgstr "Sửa đổi thông tin của tôi"
-
-#, fuzzy
-#~ msgid "Successed:"
-#~ msgstr "Tốc độ :"
-
-#~ msgid ""
-#~ "Setting custom faces is not currently supported. Please choose an image "
-#~ "from %s."
-#~ msgstr ""
-#~ "Hiện thời không hỗ trợ tính năng đặt mặt tự chọn. Hãy chọn một ảnh từ %s."
-
-#~ msgid "Invalid QQ Face"
-#~ msgstr "Mặt QQ không hợp lệ"
-
-#~ msgid "You rejected %d's request"
-#~ msgstr "Bạn đã từ chối yêu cầu của %d"
-
-#~ msgid "Reject request"
-#~ msgstr "Từ chối yêu cầu"
-
-#~ msgid "Add buddy with auth request failed"
-#~ msgstr "Không thêm được bạn chát với yêu cầu sự cho phép"
-
-#, fuzzy
-#~ msgid "Add into %d's buddy list"
-#~ msgstr "Không thể nạp danh sách bạn bè"
-
-#, fuzzy
-#~ msgid "QQ Number Error"
-#~ msgstr "Số QQ"
-
-#~ msgid "Group Description"
-#~ msgstr "Mô tả nhóm"
-
-#~ msgid "Auth"
-#~ msgstr "Phép"
-
-#~ msgid "Approve"
-#~ msgstr "Tán thành"
-
-#, fuzzy
-#~ msgid "Successed to join Qun %d, operated by admin %d"
-#~ msgstr "Quản trị %2$d đã từ chối yêu cầu tham gia nhóm %1$d của bạn"
-
-#, fuzzy
-#~ msgid "[%d] removed from Qun \"%d\""
-#~ msgstr "Bạn [%d] đã rời nhóm « %d »"
-
-#, fuzzy
-#~ msgid "[%d] added to Qun \"%d\""
-#~ msgstr "Bạn [%d] đã được thêm vào nhóm « %d »"
-
-#~ msgid "I am a member"
-#~ msgstr "Tôi là thành viên"
-
-#, fuzzy
-#~ msgid "I am requesting"
-#~ msgstr "Yêu cầu sai"
-
-#~ msgid "I am the admin"
-#~ msgstr "Tôi là quản trị"
-
-#~ msgid "Unknown status"
-#~ msgstr "Trạng thái không rõ"
-
-#, fuzzy
-#~ msgid "Remove from Qun"
-#~ msgstr "Bỏ nhóm"
-
-#~ msgid "You entered a group ID outside the acceptable range"
-#~ msgstr "Bạn đã gõ một ID nhóm nằm bên ngoài phạm vi hợp lệ"
-
-#~ msgid "Are you sure you want to leave this Qun?"
-#~ msgstr "Bạn chắc chắn muốn rời Qun này không?"
-
-#~ msgid "Do you want to approve the request?"
-#~ msgstr "Bạn có muốn tán thành yêu cầu không?"
-
-#, fuzzy
-#~ msgid "Change Qun member"
-#~ msgstr "Điện thoại"
-
-#, fuzzy
-#~ msgid "Change Qun information"
-#~ msgstr "Thông tin kệnh"
-
-#~ msgid "System Message"
-#~ msgstr "Thông điệp hệ thống"
-
-#~ msgid "<b>Last Login IP</b>: %s<br>\n"
-#~ msgstr "<b>IP đăng nhập cuối:</b> %s<br>\n"
-
-#~ msgid "<b>Last Login Time</b>: %s\n"
-#~ msgstr "<b>Thời gian đăng nhập cuối:</b> %s\n"
-
-#~ msgid "Set My Information"
-#~ msgstr "Đặt thông tin của tôi"
-
-#, fuzzy
-#~ msgid "Leave the QQ Qun"
-#~ msgstr "Để lại QQ Qun này"
-
-#~ msgid "Block this buddy"
-#~ msgstr "Chặn bạn chát này"
-
-#, fuzzy
-#~ msgid "Error password: %s"
-#~ msgstr "Lỗi khi thay đổi mật khẩu"
-
-#, fuzzy
-#~ msgid "Failed to connect all servers"
-#~ msgstr "Không kết nối được với máy phục vụ."
-
-#, fuzzy
-#~ msgid "Connecting server %s, retries %d"
-#~ msgstr ""
-#~ "Lỗi kết nối từ máy phục vụ %s:\n"
-#~ "%s"
-
-#, fuzzy
-#~ msgid "Do you approve the requestion?"
-#~ msgstr "Bạn có muốn tán thành yêu cầu không?"
-
-#, fuzzy
-#~ msgid "Do you add the buddy?"
-#~ msgstr "Bạn có muốn thêm bạn chát này không?"
-
-#, fuzzy
-#~ msgid "%s added you [%s] to buddy list"
-#~ msgstr "%s đã thêm bạn [%s] vào danh sách bạn bè của họ"
-
-#, fuzzy
-#~ msgid "QQ Budy"
-#~ msgstr "Bạn chát"
-
-#~ msgid "%s wants to add you [%s] as a friend"
-#~ msgstr "%s muốn thêm bạn [%s] như người bạn"
-
-#, fuzzy
-#~ msgid "%s is not in buddy list"
-#~ msgstr "%s không có trong danh sách bạn bè của bạn"
-
-#, fuzzy
-#~ msgid "Would you add?"
-#~ msgstr "Bạn có muốn thêm họ không?"
-
-#, fuzzy
-#~ msgid "QQ Server Notice"
-#~ msgstr "Cổng máy phục vụ"
-
-#, fuzzy
-#~ msgid "Network disconnected"
-#~ msgstr "Máy ở xa đã ngắt kết nối"
-
-#~ msgid "developer"
-#~ msgstr "nhà phát triển"
-
-#~ msgid "XMPP developer"
-#~ msgstr "Nhà phát triển XMPP"
-
-#~ msgid "Artists"
-#~ msgstr "Nghệ sĩ"
-
-#~ msgid ""
-#~ "You are using %s version %s. The current version is %s. You can get it "
-#~ "from <a href=\"%s\">%s</a><hr>"
-#~ msgstr ""
-#~ "Bạn đang sử dụng %s phiên bản %s. Phiên bản hiện thời là %s. Bạn có thể "
-#~ "lấy nó ở <a href=\"%s\">%s</a><hr>"
-
-#~ msgid "<b>ChangeLog:</b><br>%s"
-#~ msgstr "<b>Bản ghi thay đổi:</b><br>%s"
-
-#~ msgid "EOF while reading from resolver process"
-#~ msgstr "Gặp kết thúc tập tin khi đọc từ tiến trình giải quyết"
-
-#~ msgid "Your information has been updated"
-#~ msgstr "Thông tin của bạn đã được cập nhật"
-
-#~ msgid "Input your reason:"
-#~ msgstr "Gõ lý do :"
-
-#~ msgid "You have successfully removed a buddy"
-#~ msgstr "Bạn đã gỡ bỏ thành công một bạn chát"
-
-#~ msgid "You have successfully removed yourself from your friend's buddy list"
-#~ msgstr ""
-#~ "Bạn đã loại bỏ thành công bạn thân ra khỏi danh sách bạn bè của người bạn"
-
-#~ msgid "You have added %d to buddy list"
-#~ msgstr "Bạn đã thêm %d vào danh sách bạn bè"
-
-#~ msgid "Invalid QQid"
-#~ msgstr "QQid không hợp lệ"
-
-#~ msgid "Please enter external group ID"
-#~ msgstr "Hãy nhập ID nhóm bên ngoài"
-
-#~ msgid "Reason: %s"
-#~ msgstr "Lý do : %s"
-
-#~ msgid "Your request to join group %d has been approved by admin %d"
-#~ msgstr "Quản trị %2$d đã tán thành yêu cầu tham gia nhóm %1$d của bạn"
-
-#~ msgid "I am applying to join"
-#~ msgstr "Tôi muốn tham gia"
-
-#~ msgid "You have successfully left the group"
-#~ msgstr "Bạn đã rời thành công nhóm này"
-
-#~ msgid "QQ Group Auth"
-#~ msgstr "Phép nhóm QQ"
-
-#~ msgid "Your authorization request has been accepted by the QQ server"
-#~ msgstr "Máy phục vụ QQ đã chấp nhận yêu cầu sự cho phép của bạn"
-
-#~ msgid "Enter your reason:"
-#~ msgstr "Gõ lý do :"
-
-# Name: don't translate/Tên: đừng dịch
-#, fuzzy
-#~ msgid " Space"
-#~ msgstr "MySpace"
-
-#, fuzzy
-#~ msgid "<b>Real hostname</b>: %s: %d<br>\n"
-#~ msgstr "<b>IP máy phục vụ </b>: %s: %d<br>\n"
-
-#~ msgid "Show Login Information"
-#~ msgstr "Hiện thông tin đăng nhập"
-
-#~ msgid "Unable to login. Check debug log."
-#~ msgstr "Không thể đăng nhập, hãy kiểm tra sổ theo dõi gỡ lỗi"
-
-#, fuzzy
-#~ msgid "Failed room reply"
-#~ msgstr "Không đăng nhập được, không có đáp ứng"
-
-#~ msgid "User %s rejected your request"
-#~ msgstr "Người dùng %s đã từ chối yêu cầu của bạn"
-
-#~ msgid "User %s approved your request"
-#~ msgstr "Người dùng %s đã tán thành yêu cầu của bạn"
-
-#, fuzzy
-#~ msgid "Notice from: %s"
-#~ msgstr "Thông báo từ %s"
-
-#~ msgid "Error setting socket options"
-#~ msgstr "Lỗi đặt tùy chọn ổ cắm"
-
-#~ msgid ""
-#~ "Windows Live ID authentication: cannot find authenticate token in server "
-#~ "response"
-#~ msgstr ""
-#~ "Xác thực ID Windows Live: không tìm thấy hiệu bài xác thực trong đáp ứng "
-#~ "máy phục vụ"
-
-#~ msgid "Windows Live ID authentication Failed"
-#~ msgstr "Không xác thực được ID Windows Live"
-
-#~ msgid "Code [0x%02X]: %s"
-#~ msgstr "Mã [0x%02X]: %s"
-
-#~ msgid "Group Operation Error"
-#~ msgstr "Lỗi thao tác nhóm"
-
-#~ msgid "TCP Address"
-#~ msgstr "Địa chỉ TCP"
-
-#~ msgid "UDP Address"
-#~ msgstr "Địa chỉ UDP"
diff --git a/share/ca-certs/Entrust.net_Secure_Server_CA.pem b/share/ca-certs/Entrust.net_Secure_Server_CA.pem
new file mode 100644
index 0000000000..4b8939ccba
--- /dev/null
+++ b/share/ca-certs/Entrust.net_Secure_Server_CA.pem
@@ -0,0 +1,28 @@
+-----BEGIN CERTIFICATE-----
+MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC
+VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u
+ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc
+KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u
+ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05OTA1
+MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIGA1UE
+ChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5j
+b3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF
+bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUg
+U2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUA
+A4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQaO2f55M28Qpku0f1BBc/
+I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5gXpa0zf3
+wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OC
+AdcwggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHb
+oIHYpIHVMIHSMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5
+BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1p
+dHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1pdGVk
+MTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRp
+b24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu
+dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0
+MFqBDzIwMTkwNTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8Bdi
+E1U9s/8KAGv7UISX8+1i0BowHQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAa
+MAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI
+hvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyNEwr75Ji174z4xRAN
+95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9n9cd
+2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI=
+-----END CERTIFICATE-----
diff --git a/share/ca-certs/Makefile.am b/share/ca-certs/Makefile.am
index b9a0d0b17f..4ec9d6ab76 100644
--- a/share/ca-certs/Makefile.am
+++ b/share/ca-certs/Makefile.am
@@ -2,6 +2,7 @@ CERTIFICATES = \
America_Online_Root_Certification_Authority_1.pem \
CAcert_Root.pem \
CAcert_Class3.pem \
+ Entrust.net_Secure_Server_CA.pem \
Equifax_Secure_CA.pem \
Equifax_Secure_Global_eBusiness_CA-1.pem \
GTE_CyberTrust_Global_Root.pem \