Mercurial > pidgin.yaz
changeset 30944:4b7d599b5a00
merge of '745f55ef98eaf96a452eb54bb2c46f21845f9996'
and 'b04f4322926ab2a4467fb2b9c11d9961e66d1a9c'
author | Marcus Lundblad <ml@update.uu.se> |
---|---|
date | Mon, 09 Nov 2009 19:27:45 +0000 |
parents | 0712ce23901e (diff) d760797a3528 (current diff) |
children | 0b5520bf1fe3 |
files | libpurple/protocols/jabber/JEPS libpurple/protocols/jabber/jabber.c |
diffstat | 167 files changed, 14955 insertions(+), 3690 deletions(-) [+] |
line wrap: on
line diff
--- a/AUTHORS Mon Nov 09 19:27:38 2009 +0000 +++ b/AUTHORS Mon Nov 09 19:27:45 2009 +0000 @@ -20,6 +20,7 @@ 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
--- a/COPYRIGHT Mon Nov 09 19:27:38 2009 +0000 +++ b/COPYRIGHT Mon Nov 09 19:27:45 2009 +0000 @@ -268,6 +268,7 @@ Ambrose C. Li Nicolas Lichtmaier Wesley Lin +Shaun Lindsay Artem Litvinovich Josh Littlefield Daniel Ljungborg @@ -275,6 +276,7 @@ Lokheed Norberto Lopes Shlomi Loubaton +Pieter Loubser Brian Lu Uli Luckas Matthew Luckie @@ -318,6 +320,7 @@ Sergio Moretto Andrei Mozzhuhin Christian Muise +MXit Lifestyle (Pty) Ltd. Richard Nelson Dennis Nezic Matthew A. Nicholson @@ -406,11 +409,10 @@ Carsten Schaar Toby Schaffer Jonathan Schleifer <js-pidgin@webkeks.org> -Matteo Settenvini -Colin Seymour Luke Schierer Ralph Schmieder David Schmitt +Heiko Schmitt Mark Schneider Evan Schoenberg Gabriel Schulhof @@ -420,6 +422,8 @@ Peter Seebach Don Seiler Leonardo Serra +Matteo Settenvini +Colin Seymour Jim Seymour Javeed Shaikh Joe Shaw @@ -495,6 +499,7 @@ James Vega David Vermeille Sid Vicious +Andrew Victor Jorge Villaseñor (Masca) Bjoern Voigt Wan Hing Wah
--- a/ChangeLog Mon Nov 09 19:27:38 2009 +0000 +++ b/ChangeLog Mon Nov 09 19:27:45 2009 +0000 @@ -1,18 +1,74 @@ + Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul -version 2.6.3 (??/??/20??): - XMPP: - * Fix a crash when attempting to validate an invalid JID. - * Resolve an issue when connecting to iChat Server when no resource - is specified. - * Fix a crash when adding a buddy without an '@'. +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: * 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
--- a/ChangeLog.API Mon Nov 09 19:27:38 2009 +0000 +++ b/ChangeLog.API Mon Nov 09 19:27:45 2009 +0000 @@ -1,5 +1,8 @@ Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul +version 2.6.3 (10/16/2009): + No changes + version 2.6.2 (09/05/2009): Perl: Added:
--- a/ChangeLog.win32 Mon Nov 09 19:27:38 2009 +0000 +++ b/ChangeLog.win32 Mon Nov 09 19:27:45 2009 +0000 @@ -1,3 +1,6 @@ +version 2.6.3 (10/16/2009): + * No changes + version 2.6.2 (09/05/2009): * No changes
--- a/NEWS Mon Nov 09 19:27:38 2009 +0000 +++ b/NEWS Mon Nov 09 19:27:45 2009 +0000 @@ -2,6 +2,11 @@ Our development blog is available at: http://planet.pidgin.im +2.6.3 (10/16/2009): + Mark: Someone reported a fairly serious bug in our AIM/ICQ code + so we're releasing a special "severe bug fix only" build. See the + ChangeLog for details. Enjoy! + 2.6.2 (09/05/2009): Mark: Woo boy it's been a busy two weeks. There was a lot of new code in 2.6.0, and with new code comes new bugs. The cadre of relentless
--- a/autogen.sh Mon Nov 09 19:27:38 2009 +0000 +++ b/autogen.sh Mon Nov 09 19:27:45 2009 +0000 @@ -83,7 +83,7 @@ OUTPUT=`mktemp autogen-XXXXXX` - printf "%s" "running ${CMD} ${@}... " + printf "running %s %s... " ${CMD} "$*" ${CMD} ${@} >${OUTPUT} 2>&1 if [ $? != 0 ] ; then @@ -99,9 +99,17 @@ fi } +cleanup () { + rm -f autogen-?????? + echo + exit 2 +} + ############################################################################### # We really start here, yes, very sneaky! ############################################################################### +trap cleanup 2 + FIGLET=`which figlet 2> /dev/null` if [ x"${FIGLET}" != x"" ] ; then ${FIGLET} -f small ${PACKAGE} @@ -143,7 +151,7 @@ run_or_die ${INTLTOOLIZE} ${INTLTOOLIZE_FLAGS:-"-c -f --automake"} # This call to sed is needed to work around an annoying bug in intltool 0.40.6 # See http://developer.pidgin.im/ticket/9520 for details -run_or_die ${SED} "s:'\^\$\$lang\$\$':\^\$\$lang\$\$:g" -i po/Makefile.in.in +run_or_die ${SED} -i.bak -e "s:'\^\$\$lang\$\$':\^\$\$lang\$\$:g" po/Makefile.in.in run_or_die ${ACLOCAL} ${ACLOCAL_FLAGS:-"-I m4macros"} run_or_die ${AUTOHEADER} ${AUTOHEADER_FLAGS} run_or_die ${AUTOMAKE} ${AUTOMAKE_FLAGS:-"-a -c --gnu"}
--- a/configure.ac Mon Nov 09 19:27:38 2009 +0000 +++ b/configure.ac Mon Nov 09 19:27:45 2009 +0000 @@ -46,7 +46,7 @@ m4_define([purple_lt_current], [6]) m4_define([purple_major_version], [2]) m4_define([purple_minor_version], [6]) -m4_define([purple_micro_version], [3]) +m4_define([purple_micro_version], [4]) m4_define([purple_version_suffix], [devel]) m4_define([purple_version], [purple_major_version.purple_minor_version.purple_micro_version]) @@ -55,7 +55,7 @@ m4_define([gnt_lt_current], [6]) m4_define([gnt_major_version], [2]) m4_define([gnt_minor_version], [6]) -m4_define([gnt_micro_version], [3]) +m4_define([gnt_micro_version], [4]) m4_define([gnt_version_suffix], [devel]) m4_define([gnt_version], [gnt_major_version.gnt_minor_version.gnt_micro_version]) @@ -1078,7 +1078,7 @@ fi if test "x$STATIC_PRPLS" = "xall" ; then - STATIC_PRPLS="bonjour gg irc jabber msn myspace novell oscar qq sametime silc simple yahoo zephyr" + STATIC_PRPLS="bonjour gg irc jabber msn myspace mxit novell oscar qq sametime silc simple yahoo zephyr" fi if test "x$have_meanwhile" != "xyes" ; then STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/sametime//'` @@ -1135,6 +1135,7 @@ msn) static_msn=yes ;; msnp9) static_msn=yes ;; myspace) static_myspace=yes ;; + mxit) static_mxit=yes ;; novell) static_novell=yes ;; oscar) static_oscar=yes ;; aim) static_oscar=yes ;; @@ -1155,6 +1156,7 @@ AM_CONDITIONAL(STATIC_JABBER, test "x$static_jabber" = "xyes") AM_CONDITIONAL(STATIC_MSN, test "x$static_msn" = "xyes") AM_CONDITIONAL(STATIC_MYSPACE, test "x$static_myspace" = "xyes") +AM_CONDITIONAL(STATIC_MXIT, test "x$static_mxit" = "xyes") AM_CONDITIONAL(STATIC_NOVELL, test "x$static_novell" = "xyes") AM_CONDITIONAL(STATIC_OSCAR, test "x$static_oscar" = "xyes") AM_CONDITIONAL(STATIC_QQ, test "x$static_qq" = "xyes") @@ -1169,7 +1171,7 @@ AC_ARG_WITH(dynamic_prpls, [AC_HELP_STRING([--with-dynamic-prpls], [specify which protocols to build dynamically])], [DYNAMIC_PRPLS=`echo $withval | $sedpath 's/,/ /g'`]) if test "x$DYNAMIC_PRPLS" = "xall" ; then - DYNAMIC_PRPLS="bonjour gg irc jabber msn myspace novell oscar qq sametime silc simple yahoo zephyr" + DYNAMIC_PRPLS="bonjour gg irc jabber msn myspace mxit novell oscar qq sametime silc simple yahoo zephyr" fi if test "x$have_meanwhile" != "xyes"; then DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/sametime//'` @@ -1196,6 +1198,7 @@ msn) dynamic_msn=yes ;; msnp9) dynamic_msn=yes ;; myspace) dynamic_myspace=yes ;; + mxit) dynamic_mxit=yes ;; novell) dynamic_novell=yes ;; null) dynamic_null=yes ;; oscar) dynamic_oscar=yes ;; @@ -1773,6 +1776,23 @@ 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 @@ libpurple/protocols/msn/Makefile libpurple/protocols/msnp9/Makefile libpurple/protocols/myspace/Makefile + libpurple/protocols/mxit/Makefile libpurple/protocols/novell/Makefile libpurple/protocols/null/Makefile libpurple/protocols/oscar/Makefile
--- a/doc/pidgin.1.in Mon Nov 09 19:27:38 2009 +0000 +++ b/doc/pidgin.1.in Mon Nov 09 19:27:45 2009 +0000 @@ -628,6 +628,8 @@ .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)
--- a/finch/gntblist.c Mon Nov 09 19:27:38 2009 +0000 +++ b/finch/gntblist.c Mon Nov 09 19:27:45 2009 +0000 @@ -1940,7 +1940,7 @@ } 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
--- a/finch/gntconn.c Mon Nov 09 19:27:38 2009 +0000 +++ b/finch/gntconn.c Mon Nov 09 19:27:45 2009 +0000 @@ -107,7 +107,6 @@ { FinchAutoRecon *info; PurpleAccount *account = purple_connection_get_account(gc); - GList *list; if (!purple_connection_error_is_fatal(reason)) { info = g_hash_table_lookup(hash, account); @@ -144,21 +143,6 @@ g_free(secondary); purple_account_set_enabled(account, FINCH_UI, FALSE); } - - /* If we have any open chats, we probably want to rejoin when we get back online. */ - list = purple_get_chats(); - while (list) { - PurpleConversation *conv = list->data; - list = list->next; - if (purple_conversation_get_account(conv) != account || - purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv))) - continue; - purple_conversation_set_data(conv, "want-to-rejoin", GINT_TO_POINTER(TRUE)); - purple_conversation_write(conv, NULL, _("The account has disconnected and you are no " - "longer in this chat. You will be automatically rejoined in the chat when " - "the account reconnects."), - PURPLE_MESSAGE_SYSTEM, time(NULL)); - } } static void
--- a/finch/gntconv.c Mon Nov 09 19:27:38 2009 +0000 +++ b/finch/gntconv.c Mon Nov 09 19:27:45 2009 +0000 @@ -367,6 +367,28 @@ } } +static void +account_signing_off(PurpleConnection *gc) +{ + GList *list = purple_get_chats(); + PurpleAccount *account = purple_connection_get_account(gc); + + /* We are about to sign off. See which chats we are currently in, and mark + * them for rejoin on reconnect. */ + while (list) { + PurpleConversation *conv = list->data; + if (!purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv)) && + purple_conversation_get_account(conv) == account) { + purple_conversation_set_data(conv, "want-to-rejoin", GINT_TO_POINTER(TRUE)); + purple_conversation_write(conv, NULL, _("The account has disconnected and you are no " + "longer in this chat. You will be automatically rejoined in the chat when " + "the account reconnects."), + PURPLE_MESSAGE_SYSTEM, time(NULL)); + } + list = list->next; + } +} + static gpointer finch_conv_get_handle(void) { @@ -642,8 +664,25 @@ create_conv_from_userlist(GntWidget *widget, FinchConv *fc) { PurpleAccount *account = purple_conversation_get_account(fc->active_conv); - char *name = gnt_tree_get_selection_data(GNT_TREE(widget)); - purple_conversation_new(PURPLE_CONV_TYPE_IM, account, name); + PurpleConnection *gc = purple_account_get_connection(account); + PurplePluginProtocolInfo *prpl_info = NULL; + char *name, *realname; + + if (!gc) { + purple_conversation_write(fc->active_conv, NULL, _("You are not connected."), + PURPLE_MESSAGE_SYSTEM, time(NULL)); + return; + } + + name = gnt_tree_get_selection_data(GNT_TREE(widget)); + + prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl); + if (prpl_info && PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, get_cb_real_name)) + realname = prpl_info->get_cb_real_name(gc, purple_conv_chat_get_id(PURPLE_CONV_CHAT(fc->active_conv)), name); + else + realname = NULL; + purple_conversation_new(PURPLE_CONV_TYPE_IM, account, realname ? realname : name); + g_free(realname); } static void @@ -1433,6 +1472,8 @@ PURPLE_CALLBACK(account_signed_on_off), NULL); purple_signal_connect(purple_connections_get_handle(), "signed-off", finch_conv_get_handle(), PURPLE_CALLBACK(account_signed_on_off), NULL); + purple_signal_connect(purple_connections_get_handle(), "signing-off", finch_conv_get_handle(), + PURPLE_CALLBACK(account_signing_off), NULL); } void finch_conversation_uninit()
--- a/finch/libgnt/gntbindable.h Mon Nov 09 19:27:38 2009 +0000 +++ b/finch/libgnt/gntbindable.h Mon Nov 09 19:27:45 2009 +0000 @@ -166,7 +166,7 @@ * * @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.
--- a/finch/libgnt/gntbox.c Mon Nov 09 19:27:38 2009 +0000 +++ b/finch/libgnt/gntbox.c Mon Nov 09 19:27:45 2009 +0000 @@ -687,8 +687,8 @@ 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)
--- a/finch/libgnt/gntcolors.c Mon Nov 09 19:27:38 2009 +0000 +++ b/finch/libgnt/gntcolors.c Mon Nov 09 19:27:45 2009 +0000 @@ -208,8 +208,10 @@ 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 @@ 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 @@ else if (strcmp(key, "urgent") == 0) type = GNT_COLOR_URGENT; else { + g_strfreev(list); g_free(key); continue; }
--- a/finch/libgnt/gntentry.c Mon Nov 09 19:27:38 2009 +0000 +++ b/finch/libgnt/gntentry.c Mon Nov 09 19:27:45 2009 +0000 @@ -495,7 +495,7 @@ { 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 @@ snprintf(entry->start, len + 1, "%s", text); entry->end = entry->start + len; - entry->scroll = entry->start + scroll; - entry->cursor = entry->end - cursor; + if ((entry->scroll = entry->start + scroll) > entry->end) + entry->scroll = entry->end; + + if ((entry->cursor = entry->end - cursor) > entry->end) + entry->cursor = entry->end; if (GNT_WIDGET_IS_FLAG_SET(GNT_WIDGET(entry), GNT_WIDGET_MAPPED)) entry_redraw(GNT_WIDGET(entry));
--- a/finch/libgnt/gntfilesel.c Mon Nov 09 19:27:38 2009 +0000 +++ b/finch/libgnt/gntfilesel.c Mon Nov 09 19:27:45 2009 +0000 @@ -176,9 +176,13 @@ 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 @@ 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);
--- a/finch/libgnt/gnttextview.c Mon Nov 09 19:27:38 2009 +0000 +++ b/finch/libgnt/gnttextview.c Mon Nov 09 19:27:45 2009 +0000 @@ -67,6 +67,12 @@ static void reset_text_view(GntTextView *view); +static gboolean +text_view_contains(GntTextView *view, const char *str) +{ + return (str >= view->string->str && str < view->string->str + view->string->len); +} + static void gnt_text_view_draw(GntWidget *widget) { @@ -109,7 +115,7 @@ char back = *end; chtype fl = seg->flags; *end = '\0'; - if (select_start < view->string->str + seg->start && select_end > view->string->str + seg->end) { + if (select_start && select_start < view->string->str + seg->start && select_end > view->string->str + seg->end) { fl |= A_REVERSE; wattrset(widget->window, fl); wprintw(widget->window, "%s", (view->string->str + seg->start)); @@ -326,9 +332,10 @@ select_start = gnt_text_view_get_p(GNT_TEXT_VIEW(widget), x - widget->priv.x, y - widget->priv.y); g_timeout_add(500, too_slow, NULL); } else if (event == GNT_MOUSE_UP) { - if (select_start) { + GntTextView *view = GNT_TEXT_VIEW(widget); + if (text_view_contains(view, select_start)) { GString *clip; - select_end = gnt_text_view_get_p(GNT_TEXT_VIEW(widget), x - widget->priv.x, y - widget->priv.y); + select_end = gnt_text_view_get_p(view, x - widget->priv.x, y - widget->priv.y); if (select_end < select_start) { gchar *t = select_start; select_start = select_end; @@ -336,7 +343,7 @@ } if (select_start == select_end) { if (double_click) { - clip = select_word_text(GNT_TEXT_VIEW(widget), select_start); + clip = select_word_text(view, select_start); double_click = FALSE; } else { double_click = TRUE; @@ -767,6 +774,7 @@ 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 @@ 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 */
--- a/finch/libgnt/gnttree.c Mon Nov 09 19:27:38 2009 +0000 +++ b/finch/libgnt/gnttree.c Mon Nov 09 19:27:45 2009 +0000 @@ -815,7 +815,7 @@ gnt_widget_activate(widget); } else if (tree->priv->search) { gboolean changed = TRUE; - if (isalnum(*text)) { + if (g_unichar_isprint(*text)) { tree->priv->search = g_string_append_c(tree->priv->search, *text); } else if (g_utf8_collate(text, GNT_KEY_BACKSPACE) == 0) { if (tree->priv->search->len)
--- a/finch/libgnt/gntutils.c Mon Nov 09 19:27:38 2009 +0000 +++ b/finch/libgnt/gntutils.c Mon Nov 09 19:27:45 2009 +0000 @@ -374,6 +374,7 @@ gnt_widget_from_xmlnode(node, data, num); xmlFreeDoc(doc); + xmlFreeParserCtxt(ctxt); xmlCleanupParser(); va_end(list); g_free(data); @@ -470,6 +471,7 @@ xmlFreeDoc(doc); ret = TRUE; } + xmlFreeParserCtxt(ctxt); xmlCleanupParser(); return ret; #endif
--- a/finch/plugins/gnttinyurl.c Mon Nov 09 19:27:38 2009 +0000 +++ b/finch/plugins/gnttinyurl.c Mon Nov 09 19:27:45 2009 +0000 @@ -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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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) { +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_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; - GList *urls, *iter; FinchConv *fconv = FINCH_CONV(conv); GntTextView *tv = GNT_TEXT_VIEW(fconv->tv); - urls = purple_conversation_get_data(conv, "TinyURLs"); - if (!(flags & PURPLE_MESSAGE_RECV) || urls == NULL) - return; - for (iter = urls, c = 0; iter; iter = iter->next) { int i; CbInfo *cbdata; @@ -312,7 +330,6 @@ g_free(url); } g_list_free(urls); - purple_conversation_set_data(conv, "TinyURLs", NULL); } static void @@ -324,20 +341,75 @@ 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 @@ 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 @@ "Richard Nelson <wabz@whatsbeef.net>", PURPLE_WEBSITE, plugin_load, - NULL, + plugin_unload, NULL, NULL, NULL,
--- a/libpurple/Makefile.am Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/Makefile.am Mon Nov 09 19:27:45 2009 +0000 @@ -32,7 +32,7 @@ pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = purple.pc -SUBDIRS = $(GCONF_DIR) plugins protocols tests . example +SUBDIRS = $(GCONF_DIR) plugins protocols . tests example purple_coresources = \ account.c \
--- a/libpurple/account.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/account.c Mon Nov 09 19:27:45 2009 +0000 @@ -1050,9 +1050,22 @@ 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);
--- a/libpurple/blist.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/blist.c Mon Nov 09 19:27:45 2009 +0000 @@ -2009,18 +2009,14 @@ 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) { + if (!purplebuddylist->root) + node = NULL; + else + return; } - /* if we're moving to overtop of ourselves, do nothing */ - if (gnode == node) - return; - if (purple_find_group(group->name)) { /* This is just being moved */
--- a/libpurple/certificate.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/certificate.c Mon Nov 09 19:27:45 2009 +0000 @@ -97,8 +97,8 @@ "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 @@ if (flags & PURPLE_CERTIFICATE_NAME_MISMATCH) { gchar *sn = purple_certificate_get_subject_name(peer_crt); - g_string_append_printf(errors, _("The certificate claims to be " - "from \"%s\" instead. This could mean that you are " - "not connecting to the service you believe you are."), - sn); - g_free(sn); + if (sn) { + g_string_append_printf(errors, _("The certificate claims to be " + "from \"%s\" instead. This could mean that you are " + "not connecting to the service you believe you are."), + sn); + g_free(sn); - flags &= ~PURPLE_CERTIFICATE_NAME_MISMATCH; + flags &= ~PURPLE_CERTIFICATE_NAME_MISMATCH; + } } while (i != PURPLE_CERTIFICATE_LAST) {
--- a/libpurple/cipher.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/cipher.c Mon Nov 09 19:27:45 2009 +0000 @@ -50,10 +50,6 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include <glib.h> -#include <string.h> -#include <stdio.h> - #include "internal.h" #include "cipher.h" #include "dbus-maybe.h"
--- a/libpurple/dnsquery.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/dnsquery.c Mon Nov 09 19:27:45 2009 +0000 @@ -172,6 +172,7 @@ return FALSE; } +#ifdef USE_IDN static gboolean dns_str_is_ascii(const char *name) { @@ -183,6 +184,7 @@ return TRUE; } +#endif #if defined(PURPLE_DNSQUERY_USE_FORK) @@ -293,12 +295,11 @@ rc = purple_network_convert_idn_to_ascii(dns_params.hostname, &hostname); if (rc != 0) { write_to_parent(child_out, &rc, sizeof(rc)); - close(child_out); if (show_debug) fprintf(stderr, "dns[%d] Error: IDN conversion returned " "%d\n", getpid(), rc); dns_params.hostname[0] = '\0'; - continue; + break; } } else /* intentional to execute the g_strdup */ #endif @@ -323,14 +324,13 @@ rc = getaddrinfo(hostname, servname, &hints, &res); write_to_parent(child_out, &rc, sizeof(rc)); if (rc != 0) { - close(child_out); if (show_debug) printf("dns[%d] Error: getaddrinfo returned %d\n", getpid(), rc); dns_params.hostname[0] = '\0'; g_free(hostname); hostname = NULL; - continue; + break; } tmp = res; while (res) {
--- a/libpurple/dnsquery.h Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/dnsquery.h Mon Nov 09 19:27:45 2009 +0000 @@ -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; /**
--- a/libpurple/dnssrv.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/dnssrv.c Mon Nov 09 19:27:45 2009 +0000 @@ -248,6 +248,7 @@ return list; } +#ifdef USE_IDN static gboolean dns_str_is_ascii(const char *name) { @@ -259,8 +260,60 @@ return TRUE; } +#endif #ifndef _WIN32 +static void +write_to_parent(int in, int out, gconstpointer data, gsize size) +{ + const guchar *buf = data; + gssize w; + + do { + w = write(out, buf, size); + if (w > 0) { + buf += w; + size -= w; + } else if (w < 0 && errno == EINTR) { + /* Let's try some more; */ + w = 1; + } + } while (size > 0 && w > 0); + + if (size != 0) { + /* An error occurred */ + close(out); + close(in); + _exit(0); + } +} + +/* Read size bytes to data. Dies if an error occurs. */ +static void +read_from_parent(int in, int out, gpointer data, gsize size) +{ + guchar *buf = data; + gssize r; + + do { + r = read(in, data, size); + if (r > 0) { + buf += r; + size -= r; + } else if (r < 0 && errno == EINTR) { + /* Let's try some more; */ + r = 1; + } + } while (size > 0 && r > 0); + + if (size != 0) { + /* An error occurred */ + close(out); + close(in); + _exit(0); + } +} + G_GNUC_NORETURN static void resolve(int in, int out) @@ -279,16 +332,12 @@ purple_restore_default_signal_handlers(); #endif - if (read(in, &query, sizeof(query)) <= 0) { - close(out); - close(in); - _exit(0); - } + read_from_parent(in, out, &query, sizeof(query)); size = res_query( query.query, C_IN, query.type, (u_char*)&answer, sizeof( answer)); if (size == -1) { - write(out, &(query.type), sizeof(query.type)); - write(out, &size, sizeof(int)); + write_to_parent(in, out, &(query.type), sizeof(query.type)); + write_to_parent(in, out, &size, sizeof(size)); close(out); close(in); _exit(0); @@ -353,16 +402,18 @@ if (query.type == T_SRV) ret = purple_srv_sort(ret); - /* TODO: Check return value */ - write(out, &(query.type), sizeof(query.type)); - write(out, &size, sizeof(size)); + write_to_parent(in, out, &(query.type), sizeof(query.type)); + write_to_parent(in, out, &size, sizeof(size)); while (ret != NULL) { - /* TODO: Check return value */ if (query.type == T_SRV) - write(out, ret->data, sizeof(PurpleSrvResponse)); - if (query.type == T_TXT) - write(out, ret->data, sizeof(PurpleTxtResponse)); + write_to_parent(in, out, ret->data, sizeof(PurpleSrvResponse)); + if (query.type == T_TXT) { + PurpleTxtResponse *response = ret->data; + gsize l = strlen(response->content) + 1 /* null byte */; + write_to_parent(in, out, &l, sizeof(l)); + write_to_parent(in, out, response->content, l); + } g_free(ret->data); ret = g_list_remove(ret, ret->data); @@ -429,21 +480,38 @@ PurpleTxtCallback cb = query_data->cb.txt; ssize_t red; purple_debug_info("dnssrv","found %d TXT entries\n", size); - res = g_new0(PurpleTxtResponse, 1); for (i = 0; i < size; i++) { - red = read(source, res, sizeof(PurpleTxtResponse)); - if (red != sizeof(PurpleTxtResponse)) { + gsize len; + + red = read(source, &len, sizeof(len)); + if (red != sizeof(len)) { purple_debug_error("dnssrv","unable to read txt " - "response: %s\n", g_strerror(errno)); + "response length: %s\n", g_strerror(errno)); size = 0; - g_free(res); g_list_foreach(responses, (GFunc)purple_txt_response_destroy, NULL); g_list_free(responses); responses = NULL; break; } + + res = g_new0(PurpleTxtResponse, 1); + res->content = g_new0(gchar, len); + + red = read(source, res->content, len); + if (red != len) { + purple_debug_error("dnssrv","unable to read txt " + "response: %s\n", g_strerror(errno)); + size = 0; + purple_txt_response_destroy(res); + g_list_foreach(responses, (GFunc)purple_txt_response_destroy, NULL); + g_list_free(responses); + responses = NULL; + break; + } + responses = g_list_prepend(responses, res); } + responses = g_list_reverse(responses); cb(responses, query_data->extradata); } else { purple_debug_error("dnssrv", "type unknown of DNS result entry; errno is %i\n", errno); @@ -674,6 +742,7 @@ 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 @@ internal_query.type = T_TXT; strncpy(internal_query.query, query, 255); + internal_query.query[255] = '\0'; if (write(in[1], &internal_query, sizeof(internal_query)) < 0) purple_debug_error("dnssrv", "Could not write to TXT resolver\n");
--- a/libpurple/ft.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/ft.c Mon Nov 09 19:27:45 2009 +0000 @@ -1030,7 +1030,7 @@ * watcher. */ if (xfer->watcher != 0) { - purple_timeout_remove(xfer->watcher); + purple_input_remove(xfer->watcher); xfer->watcher = 0; }
--- a/libpurple/ft.h Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/ft.h Mon Nov 09 19:27:45 2009 +0000 @@ -674,7 +674,7 @@ void purple_xfer_ui_ready(PurpleXfer *xfer); /** - * Allows the prpl to signal it's readh to send/receive data (depending on + * Allows the prpl to signal it's ready to send/receive data (depending on * the direction of the file transfer. Used when the prpl provides read/write * ops and cannot/does not provide a raw fd to the core. *
--- a/libpurple/media.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/media.c Mon Nov 09 19:27:45 2009 +0000 @@ -103,6 +103,8 @@ gboolean initiator; gboolean accepted; gboolean candidates_prepared; + gboolean held; + gboolean paused; GList *active_local_candidates; GList *active_remote_candidates; @@ -281,7 +283,7 @@ { PURPLE_MEDIA_INFO_HOLD, "PURPLE_MEDIA_INFO_HOLD", "hold" }, { PURPLE_MEDIA_INFO_UNHOLD, - "PURPLE_MEDIA_INFO_HOLD", "unhold" }, + "PURPLE_MEDIA_INFO_UNHOLD", "unhold" }, { 0, NULL, NULL } }; type = g_enum_register_static("PurpleMediaInfoType", values); @@ -2330,11 +2332,46 @@ for (; streams; streams = g_list_delete_link(streams, streams)) { PurpleMediaStream *stream = streams->data; if (stream->session->type & PURPLE_MEDIA_SEND_VIDEO) { + stream->paused = active; + + if (!stream->held) + g_object_set(stream->stream, "direction", + purple_media_to_fs_stream_direction( + stream->session->type & ((active) ? + ~PURPLE_MEDIA_SEND_VIDEO : + PURPLE_MEDIA_VIDEO)), NULL); + } + } + } else if (local == TRUE && (type == PURPLE_MEDIA_INFO_HOLD || + type == PURPLE_MEDIA_INFO_UNHOLD)) { + GList *streams; + gboolean active = (type == PURPLE_MEDIA_INFO_HOLD); + + g_return_if_fail(PURPLE_IS_MEDIA(media)); + + streams = purple_media_get_streams(media, + session_id, participant); + for (; streams; streams = g_list_delete_link(streams, streams)) { + PurpleMediaStream *stream = streams->data; + stream->held = active; + if (stream->session->type & PURPLE_MEDIA_VIDEO) { + FsStreamDirection direction; + + direction = ((active) ? + ~PURPLE_MEDIA_VIDEO : + PURPLE_MEDIA_VIDEO); + if (!active && stream->paused) + direction &= ~PURPLE_MEDIA_SEND_VIDEO; + + g_object_set(stream->stream, "direction", + purple_media_to_fs_stream_direction( + stream->session->type & direction), NULL); + } else if (stream->session->type & PURPLE_MEDIA_AUDIO) { g_object_set(stream->stream, "direction", purple_media_to_fs_stream_direction( stream->session->type & ((active) ? - ~PURPLE_MEDIA_SEND_VIDEO : - PURPLE_MEDIA_VIDEO)), NULL); + ~PURPLE_MEDIA_AUDIO : + PURPLE_MEDIA_AUDIO)), NULL); } } }
--- a/libpurple/plugins/perl/Makefile.mingw Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/plugins/perl/Makefile.mingw Mon Nov 09 19:27:45 2009 +0000 @@ -7,10 +7,12 @@ PIDGIN_TREE_TOP := ../../.. include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak +DEFINES := $(subst -DWIN32_LEAN_AND_MEAN,,$(DEFINES)) + TARGET = perl # Perl headers with /* /* */ type comments.. Turn off warnings. -CFLAGS += -Wno-comment +GCCWARNINGS += -Wno-comment ## ## INCLUDE PATHS
--- a/libpurple/plugins/perl/common/Makefile.mingw Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/plugins/perl/common/Makefile.mingw Mon Nov 09 19:27:45 2009 +0000 @@ -5,9 +5,12 @@ # PIDGIN_TREE_TOP := ../../../.. -GCCWARNINGS := -Wno-comment -Waggregate-return -Wcast-align -Wdeclaration-after-statement -Werror-implicit-function-declaration -Wextra -Wno-sign-compare -Wno-unused-parameter -Winit-self -Wmissing-declarations -Wmissing-prototypes -Wpointer-arith -Wundef -Wno-unused include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak +GCCWARNINGS += -Wno-comment -Wno-unused -Wno-nested-externs + +DEFINES := $(subst -DWIN32_LEAN_AND_MEAN,,$(DEFINES)) + TARGET = Purple AUTOSPLIT = lib/auto/Purple/autosplit.ix EXTUTILS ?= C:/perl/lib/ExtUtils
--- a/libpurple/plugins/perl/common/Prpl.xs Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/plugins/perl/common/Prpl.xs Mon Nov 09 19:27:45 2009 +0000 @@ -62,11 +62,15 @@ 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
--- a/libpurple/plugins/perl/perl-handlers.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/plugins/perl/perl-handlers.c Mon Nov 09 19:27:45 2009 +0000 @@ -649,6 +649,7 @@ static void destroy_cmd_handler(PurplePerlCmdHandler *handler) { + purple_cmd_unregister(handler->id); cmd_handlers = g_slist_remove(cmd_handlers, handler); if (handler->callback != NULL) @@ -705,7 +706,6 @@ return; } - purple_cmd_unregister(id); destroy_cmd_handler(handler); }
--- a/libpurple/plugins/perl/perl.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/plugins/perl/perl.c Mon Nov 09 19:27:45 2009 +0000 @@ -621,6 +621,9 @@ g_free(gps); plugin->info->extra_info = NULL; } + + g_free(plugin->info); + plugin->info = NULL; } }
--- a/libpurple/plugins/ssl/ssl-gnutls.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/plugins/ssl/ssl-gnutls.c Mon Nov 09 19:27:45 2009 +0000 @@ -698,9 +698,8 @@ 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 @@ 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 @@ "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
--- a/libpurple/protocols/Makefile.am Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/Makefile.am Mon Nov 09 19:27:45 2009 +0000 @@ -1,5 +1,5 @@ EXTRA_DIST = Makefile.mingw -DIST_SUBDIRS = bonjour gg irc jabber msn msnp9 myspace novell null oscar qq sametime silc silc10 simple yahoo zephyr +DIST_SUBDIRS = bonjour gg irc jabber msn msnp9 myspace mxit novell null oscar qq sametime silc silc10 simple yahoo zephyr SUBDIRS = $(DYNAMIC_PRPLS) $(STATIC_PRPLS)
--- a/libpurple/protocols/bonjour/mdns_avahi.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/bonjour/mdns_avahi.c Mon Nov 09 19:27:45 2009 +0000 @@ -150,6 +150,10 @@ } break; case AVAHI_RESOLVER_FOUND: + + purple_debug_info("bonjour", "_resolve_callback - name:%s account:%p bb:%p\n", + name, account, bb); + /* create a buddy record */ if (bb == NULL) bb = bonjour_buddy_new(name, account); @@ -173,8 +177,12 @@ /* Get the ip as a string */ + ip[0] = '\0'; avahi_address_snprint(ip, AVAHI_ADDRESS_STR_MAX, a); + purple_debug_info("bonjour", "_resolve_callback - name:%s ip:%s prev_ip:%s\n", + name, ip, rd->ip); + if (rd->ip == NULL || strcmp(rd->ip, ip) != 0) { /* We store duplicates in bb->ips, so we always remove the one */ if (rd->ip != NULL) {
--- a/libpurple/protocols/jabber/JEPS Mon Nov 09 19:27:38 2009 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ -0045: IN PROGRESS - Multi-User Chat -0047: IN PROGRESS - In-Band Bytestreams -0060: NEED - Pub-Sub -0071: AWAITING FINAL SPEC - XHTML-IM -0073: NEED - Basic IM Protocol Suite -0080: NEED (Do we?) - Geographic Location Information -0084: NEED - User Avatars in Jabber -0085: NEED - Chat State Notifications -0089: WATCH - Generic Alerts -0093: NEED - Roster Item Exchange -0100: NEED - Gateway Interaction (Transports) -0115: WATCH - Client Capabilities -0117: NEED - Intermediate IM Protocol Suite -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/XEPS Mon Nov 09 19:27:45 2009 +0000 @@ -0,0 +1,4 @@ +0060: NEED + Pub-Sub +0080: NEED (Do we?) + Geographic Location Information
--- a/libpurple/protocols/jabber/auth.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/jabber/auth.c Mon Nov 09 19:27:45 2009 +0000 @@ -58,7 +58,7 @@ PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, _("Server requires TLS/SSL, but no TLS/SSL support was found.")); return TRUE; - } else if(purple_account_get_bool(js->gc->account, "require_tls", FALSE)) { + } else if(purple_account_get_bool(js->gc->account, "require_tls", JABBER_DEFAULT_REQUIRE_TLS)) { purple_connection_error_reason(js->gc, PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, _("You require encryption, but no TLS/SSL support was found.")); @@ -381,13 +381,13 @@ * due to mechanism specific issues, so we want to try one of the other * supported mechanisms. This code handles that case */ - if (js->current_mech && strlen(js->current_mech) > 0) { + if (js->current_mech && *js->current_mech) { char *pos; if ((pos = strstr(js->sasl_mechs->str, js->current_mech))) { g_string_erase(js->sasl_mechs, pos-js->sasl_mechs->str, strlen(js->current_mech)); } /* Remove space which separated this mech from the next */ - if (strlen(js->sasl_mechs->str) > 0 && ((js->sasl_mechs->str)[0] == ' ')) { + if ((js->sasl_mechs->str)[0] == ' ') { g_string_erase(js->sasl_mechs, 0, 1); } again = TRUE; @@ -511,7 +511,7 @@ * support it and including it gives a false fall-back to other mechs offerred, * leading to incorrect error handling. */ - if (mech_name && !strcmp(mech_name, "X-GOOGLE-TOKEN")) { + if (purple_strequal(mech_name, "X-GOOGLE-TOKEN")) { g_free(mech_name); continue; } @@ -519,9 +519,9 @@ g_string_append(js->sasl_mechs, mech_name); g_string_append_c(js->sasl_mechs, ' '); #else - if(mech_name && !strcmp(mech_name, "DIGEST-MD5")) + if (purple_strequal(mech_name, "DIGEST-MD5")) digest_md5 = TRUE; - else if(mech_name && !strcmp(mech_name, "PLAIN")) + else if (purple_strequal(mech_name, "PLAIN")) plain = TRUE; #endif g_free(mech_name); @@ -586,7 +586,7 @@ /* FIXME: Why is this not in jabber_parse_error? */ if((error = xmlnode_get_child(packet, "error")) && (err_code = xmlnode_get_attrib(error, "code")) && - !strcmp(err_code, "401")) { + g_str_equal(err_code, "401")) { reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED; /* Clear the pasword if it isn't being saved */ if (!purple_account_get_remember_password(js->gc->account)) @@ -698,7 +698,7 @@ * is requiring SSL/TLS, we need to enforce it. */ if (!jabber_stream_is_ssl(js) && - purple_account_get_bool(purple_connection_get_account(js->gc), "require_tls", FALSE)) { + purple_account_get_bool(purple_connection_get_account(js->gc), "require_tls", JABBER_DEFAULT_REQUIRE_TLS)) { purple_connection_error_reason(js->gc, PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR, _("You require encryption, but it is not available on this server.")); @@ -877,7 +877,7 @@ } dec_in = (char *)purple_base64_decode(enc_in, NULL); - purple_debug(PURPLE_DEBUG_MISC, "jabber", "decoded challenge (%" + purple_debug_misc("jabber", "decoded challenge (%" G_GSIZE_FORMAT "): %s\n", strlen(dec_in), dec_in); parts = parse_challenge(dec_in); @@ -887,8 +887,7 @@ char *rspauth = g_hash_table_lookup(parts, "rspauth"); - if(rspauth && js->expected_rspauth && - !strcmp(rspauth, js->expected_rspauth)) { + if (rspauth && purple_strequal(rspauth, js->expected_rspauth)) { jabber_send_raw(js, "<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", -1); @@ -1014,7 +1013,7 @@ * realm are always encoded in UTF-8 (they seem to be the values * we pass in), so we need to ensure charset=utf-8 is set. */ - if (!js->current_mech || !g_str_equal(js->current_mech, "DIGEST-MD5") || + if (!purple_strequal(js->current_mech, "DIGEST-MD5") || strstr(c_out, ",charset=")) /* If we're not using DIGEST-MD5 or Cyrus SASL is fixed */ enc_out = purple_base64_encode((unsigned char*)c_out, clen); @@ -1041,7 +1040,7 @@ const void *x; #endif - if(!ns || strcmp(ns, "urn:ietf:params:xml:ns:xmpp-sasl")) { + if (!purple_strequal(ns, "urn:ietf:params:xml:ns:xmpp-sasl")) { purple_connection_error_reason(js->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Invalid response from server")); @@ -1072,6 +1071,7 @@ purple_connection_error_reason(js->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Invalid response from server")); + g_return_if_reached(); } } /* If we've negotiated a security layer, we need to enable it */ @@ -1099,17 +1099,17 @@ #ifdef HAVE_CYRUS_SASL if(js->auth_fail_count++ < 5) { - if (js->current_mech && strlen(js->current_mech) > 0) { + if (js->current_mech && *js->current_mech) { char *pos; if ((pos = strstr(js->sasl_mechs->str, js->current_mech))) { g_string_erase(js->sasl_mechs, pos-js->sasl_mechs->str, strlen(js->current_mech)); } /* Remove space which separated this mech from the next */ - if (strlen(js->sasl_mechs->str) > 0 && ((js->sasl_mechs->str)[0] == ' ')) { + if ((js->sasl_mechs->str)[0] == ' ') { g_string_erase(js->sasl_mechs, 0, 1); } } - if (strlen(js->sasl_mechs->str)) { + if (*js->sasl_mechs->str) { /* If we have remaining mechs to try, do so */ sasl_dispose(&js->sasl);
--- a/libpurple/protocols/jabber/bosh.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/jabber/bosh.c Mon Nov 09 19:27:45 2009 +0000 @@ -401,7 +401,8 @@ 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) {
--- a/libpurple/protocols/jabber/buddy.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/jabber/buddy.c Mon Nov 09 19:27:45 2009 +0000 @@ -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 @@ 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 @@ 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 @@ 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 *
--- a/libpurple/protocols/jabber/caps.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/jabber/caps.c Mon Nov 09 19:27:45 2009 +0000 @@ -975,7 +975,7 @@ g_free(js->caps_hash); js->caps_hash = jabber_caps_calculate_hash(&info, "sha1"); g_list_free(info.identities); - g_list_free(features); + g_list_free(info.features); } const gchar* jabber_caps_get_own_hash(JabberStream *js)
--- a/libpurple/protocols/jabber/chat.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/jabber/chat.c Mon Nov 09 19:27:45 2009 +0000 @@ -106,7 +106,7 @@ { char *room_jid = g_strdup_printf("%s@%s", room, server); - chat = g_hash_table_lookup(js->chats, jabber_normalize(NULL, room_jid)); + chat = g_hash_table_lookup(js->chats, room_jid); g_free(room_jid); } @@ -177,10 +177,21 @@ xmlnode_insert_data(body, msg, -1); } else { xmlnode_set_attrib(message, "to", name); + /* + * Putting the reason into the body was an 'undocumented protocol, + * ...not part of "groupchat 1.0"'. + * http://xmpp.org/extensions/attic/jep-0045-1.16.html#invite + * + * Left here for compatibility. + */ body = xmlnode_new_child(message, "body"); xmlnode_insert_data(body, msg, -1); + x = xmlnode_new_child(message, "x"); xmlnode_set_attrib(x, "jid", room_jid); + + /* The better place for it! XEP-0249 style. */ + xmlnode_set_attrib(x, "reason", msg); xmlnode_set_namespace(x, "jabber:x:conference"); } @@ -209,19 +220,98 @@ 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; + 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; - char *tmp, *room_jid, *full_jid; - JabberStream *js = gc->proto_data; - PurplePresence *gpresence; - PurpleStatus *status; 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 @@ 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 @@ 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_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 @@ 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 @@ jabber_send(chat->js, presence); xmlnode_free(presence); + + return TRUE; } void jabber_chat_part(JabberChat *chat, const char *msg)
--- a/libpurple/protocols/jabber/chat.h Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/jabber/chat.h Mon Nov 09 19:27:45 2009 +0000 @@ -57,6 +57,21 @@ 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_register(JabberChat *chat); void jabber_chat_change_topic(JabberChat *chat, const char *topic); void jabber_chat_set_topic(PurpleConnection *gc, int id, const char *topic); -void jabber_chat_change_nick(JabberChat *chat, const char *nick); +gboolean jabber_chat_change_nick(JabberChat *chat, const char *nick); void jabber_chat_part(JabberChat *chat, const char *msg); void jabber_chat_track_handle(JabberChat *chat, const char *handle, const char *jid, const char *affiliation, const char *role);
--- a/libpurple/protocols/jabber/disco.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/jabber/disco.c Mon Nov 09 19:27:45 2009 +0000 @@ -421,6 +421,76 @@ } +/* should probably share this code with google.c, or maybe from 2.7.0 + introduce an abstracted hostname -> IP function in dns.c */ +static void +jabber_disco_stun_lookup_cb(GSList *hosts, gpointer data, + const char *error_message) +{ + JabberStream *js = (JabberStream *) data; + + if (error_message) { + purple_debug_error("jabber", "STUN lookup failed: %s\n", + error_message); + g_slist_free(hosts); + js->stun_query = NULL; + return; + } + + if (hosts && g_slist_next(hosts)) { + struct sockaddr *addr = g_slist_next(hosts)->data; + char dst[INET6_ADDRSTRLEN]; + int port; + + if (addr->sa_family == AF_INET6) { + inet_ntop(addr->sa_family, &((struct sockaddr_in6 *) addr)->sin6_addr, + dst, sizeof(dst)); + port = ntohs(((struct sockaddr_in6 *) addr)->sin6_port); + } else { + inet_ntop(addr->sa_family, &((struct sockaddr_in *) addr)->sin_addr, + dst, sizeof(dst)); + port = ntohs(((struct sockaddr_in *) addr)->sin_port); + } + + if (js->stun_ip) + g_free(js->stun_ip); + js->stun_ip = g_strdup(dst); + js->stun_port = port; + + purple_debug_info("jabber", "set STUN IP/port address: " + "%s:%d\n", dst, port); + + /* unmark ongoing query */ + js->stun_query = NULL; + } + + while (hosts != NULL) { + hosts = g_slist_delete_link(hosts, hosts); + /* Free the address */ + g_free(hosts->data); + hosts = g_slist_delete_link(hosts, hosts); + } +} + + +static void +jabber_disco_stun_srv_resolve_cb(PurpleSrvResponse *resp, int results, gpointer data) +{ + JabberStream *js = (JabberStream *) data; + + purple_debug_info("jabber", "got %d SRV responses for STUN.\n", results); + js->srv_query_data = NULL; + + if (results > 0) { + purple_debug_info("jabber", "looking up IP for %s:%d\n", + resp[0].hostname, resp[0].port); + js->stun_query = + purple_dnsquery_a(resp[0].hostname, resp[0].port, + jabber_disco_stun_lookup_cb, js); + } +} + + static void jabber_disco_server_info_result_cb(JabberStream *js, const char *from, JabberIqType type, const char *id, @@ -471,7 +541,10 @@ /* autodiscover stun and relays */ jabber_google_send_jingle_info(js); } else { - /* TODO: add external service discovery here... */ + js->srv_query_data = + purple_srv_resolve("stun", "udp", js->user->domain, + jabber_disco_stun_srv_resolve_cb, js); + /* TODO: add TURN support later... */ } }
--- a/libpurple/protocols/jabber/google.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/jabber/google.c Mon Nov 09 19:27:45 2009 +0000 @@ -31,6 +31,7 @@ #include "jabber.h" #include "presence.h" #include "iq.h" +#include "chat.h" #include "jingle/jingle.h" @@ -97,14 +98,14 @@ 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 @@ 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 @@ jabber_iq_send(iq); } + purple_media_candidate_list_free(candidates); } static void @@ -1075,7 +1077,7 @@ xmlnode_set_attrib(iq->node, "id", id); jabber_iq_send(iq); - purple_debug(PURPLE_DEBUG_MISC, "jabber", + purple_debug_misc("jabber", "Got new mail notification. Sending request for more info\n"); iq = jabber_iq_new_query(js, JABBER_IQ_GET, "google:mail:notify"); @@ -1159,8 +1161,9 @@ const char *grt = xmlnode_get_attrib_with_namespace(item, "t", "google:roster"); const char *subscription = xmlnode_get_attrib(item, "subscription"); + const char *ask = xmlnode_get_attrib(item, "ask"); - if (!subscription || !strcmp(subscription, "none")) { + if ((!subscription || !strcmp(subscription, "none")) && !ask) { /* The Google Talk servers will automatically add people from your Gmail address book * with subscription=none. If we see someone with subscription=none, ignore them. */ @@ -1258,12 +1261,13 @@ jbr = l->data; if (jbr && jbr->name) { - purple_debug(PURPLE_DEBUG_MISC, "jabber", "Removing resource %s\n", jbr->name); + purple_debug_misc("jabber", "Removing resource %s\n", jbr->name); jabber_buddy_remove_resource(jb, jbr->name); } l = l->next; } } + purple_prpl_got_user_status(purple_connection_get_account(gc), who, "offline", NULL); } @@ -1607,3 +1611,43 @@ 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); +}
--- a/libpurple/protocols/jabber/google.h Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/jabber/google.h Mon Nov 09 19:27:45 2009 +0000 @@ -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 @@ xmlnode *child); void jabber_google_send_jingle_info(JabberStream *js); +void google_buddy_node_chat(PurpleBlistNode *node, gpointer data); + #endif /* PURPLE_JABBER_GOOGLE_H_ */
--- a/libpurple/protocols/jabber/iq.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/jabber/iq.c Mon Nov 09 19:27:45 2009 +0000 @@ -342,7 +342,7 @@ return; } - signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(jabber_plugin, + signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(purple_connection_get_prpl(js->gc), "jabber-receiving-iq", js->gc, iq_type, id, from, packet)); if (signal_return) return; @@ -367,7 +367,7 @@ g_free(key); if (signal_ref > 0) { - signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(jabber_plugin, "jabber-watched-iq", + signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(purple_connection_get_prpl(js->gc), "jabber-watched-iq", js->gc, iq_type, id, from, child)); if (signal_return) return;
--- a/libpurple/protocols/jabber/jabber.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/jabber/jabber.c Mon Nov 09 19:27:45 2009 +0000 @@ -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 @@ 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 @@ 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 @@ { 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 @@ 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 @@ 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 @@ *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 @@ /* 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(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 @@ 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 @@ 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 @@ } #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 @@ 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_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 @@ purple_connection_set_state(js->gc, PURPLE_CONNECTED); break; } + +#undef JABBER_CONNECT_STEPS } char *jabber_get_next_id(JabberStream *js) @@ -1857,7 +1846,7 @@ JabberFeature *feature = jabber_features->data; g_free(feature->namespace); g_free(feature); - jabber_features = g_list_remove_link(jabber_features, jabber_features); + jabber_features = g_list_delete_link(jabber_features, jabber_features); } } @@ -1894,7 +1883,7 @@ g_free(id->lang); g_free(id->name); g_free(id); - jabber_identities = g_list_remove_link(jabber_identities, jabber_identities); + jabber_identities = g_list_delete_link(jabber_identities, jabber_identities); } } @@ -2638,8 +2627,15 @@ if(!chat || !args || !args[0]) return PURPLE_CMD_RET_FAILED; - jabber_chat_change_nick(chat, args[0]); - return PURPLE_CMD_RET_OK; + if (!jabber_resourceprep_validate(args[0])) { + *error = g_strdup(_("Invalid nickname")); + return PURPLE_CMD_RET_FAILED; + } + + if (jabber_chat_change_nick(chat, args[0])) + return PURPLE_CMD_RET_OK; + else + return PURPLE_CMD_RET_FAILED; } static PurpleCmdRet jabber_cmd_chat_part(PurpleConversation *conv, @@ -3274,7 +3270,7 @@ id = purple_cmd_register("part", "s", PURPLE_CMD_P_PRPL, PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber", - jabber_cmd_chat_part, _("part [room]: Leave the room."), + jabber_cmd_chat_part, _("part [message]: Leave the room."), NULL); jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id)); @@ -3429,8 +3425,6 @@ unspecified */ const gchar *ui_name = NULL; - jabber_plugin = plugin; - ui_type = ui_info ? g_hash_table_lookup(ui_info, "client_type") : NULL; if (ui_type) { if (strcmp(ui_type, "pc") == 0 || @@ -3520,9 +3514,9 @@ } void -jabber_uninit_plugin(void) +jabber_uninit_plugin(PurplePlugin *plugin) { - purple_plugin_ipc_unregister_all(jabber_plugin); + purple_plugin_ipc_unregister_all(plugin); jabber_features_destroy(); jabber_identities_destroy();
--- a/libpurple/protocols/jabber/jabber.h Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/jabber/jabber.h Mon Nov 09 19:27:45 2009 +0000 @@ -76,11 +76,11 @@ #define CAPS0115_NODE "http://pidgin.im/" +#define JABBER_DEFAULT_REQUIRE_TLS TRUE + /* Index into attention_types list */ #define JABBER_BUZZ 0 -extern PurplePlugin *jabber_plugin; - typedef enum { JABBER_STREAM_OFFLINE, JABBER_STREAM_CONNECTING, @@ -193,25 +193,16 @@ char *serverFQDN; - /* OK, this stays at the end of the struct, so plugins can depend - * on the rest of the stuff being in the right place - */ #ifdef HAVE_CYRUS_SASL sasl_conn_t *sasl; sasl_callback_t *sasl_cb; -#else /* keep the struct the same size */ - void *sasl; - void *sasl_cb; -#endif - /* did someone say something about the end of the struct? */ -#ifdef HAVE_CYRUS_SASL const char *current_mech; int auth_fail_count; -#endif int sasl_state; int sasl_maxbuf; GString *sasl_mechs; +#endif gboolean unregistration; PurpleAccountUnregistrationCb unregistration_cb; @@ -387,6 +378,6 @@ void jabber_unregister_commands(void); void jabber_init_plugin(PurplePlugin *plugin); -void jabber_uninit_plugin(void); +void jabber_uninit_plugin(PurplePlugin *plugin); #endif /* PURPLE_JABBER_H_ */
--- a/libpurple/protocols/jabber/jingle/content.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/jabber/jingle/content.c Mon Nov 09 19:27:45 2009 +0000 @@ -330,8 +330,8 @@ 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); }
--- a/libpurple/protocols/jabber/libxmpp.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/jabber/libxmpp.c Mon Nov 09 19:27:45 2009 +0000 @@ -227,7 +227,7 @@ jabber_unregister_commands(); /* Stay on target...stay on target... Almost there... */ - jabber_uninit_plugin(); + jabber_uninit_plugin(plugin); return TRUE; } @@ -355,7 +355,7 @@ purple_account_user_split_set_reverse(split, FALSE); prpl_info.user_splits = g_list_append(prpl_info.user_splits, split); - option = purple_account_option_bool_new(_("Require SSL/TLS"), "require_tls", TRUE); + option = purple_account_option_bool_new(_("Require SSL/TLS"), "require_tls", JABBER_DEFAULT_REQUIRE_TLS); prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
--- a/libpurple/protocols/jabber/message.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/jabber/message.c Mon Nov 09 19:27:45 2009 +0000 @@ -545,7 +545,7 @@ to = xmlnode_get_attrib(packet, "to"); type = xmlnode_get_attrib(packet, "type"); - signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(jabber_plugin, + signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(purple_connection_get_prpl(js->gc), "jabber-receiving-message", js->gc, type, id, from, to, packet)); if (signal_return) return; @@ -758,9 +758,22 @@ jm->type != JABBER_MESSAGE_ERROR) { const char *jid = xmlnode_get_attrib(child, "jid"); if(jid) { + const char *reason = xmlnode_get_attrib(child, "reason"); + const char *password = xmlnode_get_attrib(child, "password"); + jm->type = JABBER_MESSAGE_GROUPCHAT_INVITE; g_free(jm->to); jm->to = g_strdup(jid); + + if (reason) { + g_free(jm->body); + jm->body = g_strdup(reason); + } + + if (password) { + g_free(jm->password); + jm->password = g_strdup(password); + } } } else if(!strcmp(xmlns, "http://jabber.org/protocol/muc#user") && jm->type != JABBER_MESSAGE_ERROR) { @@ -775,8 +788,10 @@ g_free(jm->body); jm->body = xmlnode_get_data(reason); } - if((password = xmlnode_get_child(child, "password"))) + if((password = xmlnode_get_child(child, "password"))) { + g_free(jm->password); jm->password = xmlnode_get_data(password); + } jm->type = JABBER_MESSAGE_GROUPCHAT_INVITE; } @@ -797,7 +812,7 @@ switch(jm->type) { case JABBER_MESSAGE_OTHER: - purple_debug(PURPLE_DEBUG_INFO, "jabber", + purple_debug_info("jabber", "Received message of unknown type: %s\n", type); /* Fall-through is intentional */ case JABBER_MESSAGE_NORMAL: @@ -1088,7 +1103,7 @@ if ((child = xmlnode_from_str(jm->xhtml, -1))) { xmlnode_insert_child(message, child); } else { - purple_debug(PURPLE_DEBUG_ERROR, "jabber", + purple_debug_error("jabber", "XHTML translation/validation failed, returning: %s\n", jm->xhtml); }
--- a/libpurple/protocols/jabber/oob.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/jabber/oob.c Mon Nov 09 19:27:45 2009 +0000 @@ -95,7 +95,7 @@ if(len < 0 && errno == EAGAIN) return; else if(len < 0) { - purple_debug(PURPLE_DEBUG_ERROR, "jabber", "Write error on oob xfer!\n"); + purple_debug_error("jabber", "Write error on oob xfer!\n"); purple_input_remove(jox->writeh); purple_xfer_cancel_local(xfer); } @@ -150,7 +150,7 @@ } return 0; } else if (errno != EAGAIN) { - purple_debug(PURPLE_DEBUG_ERROR, "jabber", "Read error on oob xfer!\n"); + purple_debug_error("jabber", "Read error on oob xfer!\n"); purple_xfer_cancel_local(xfer); }
--- a/libpurple/protocols/jabber/parser.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/jabber/parser.c Mon Nov 09 19:27:45 2009 +0000 @@ -62,11 +62,6 @@ 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 @@ 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); + } }
--- a/libpurple/protocols/jabber/presence.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/jabber/presence.c Mon Nov 09 19:27:45 2009 +0000 @@ -403,19 +403,20 @@ 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 @@ /* * 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 @@ jb = jabber_buddy_find(js, from, TRUE); g_return_if_fail(jb != NULL); - signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(jabber_plugin, + signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(purple_connection_get_prpl(js->gc), "jabber-receiving-presence", js->gc, type, from, packet)); if (signal_return) return;
--- a/libpurple/protocols/jabber/roster.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/jabber/roster.c Mon Nov 09 19:27:45 2009 +0000 @@ -72,7 +72,7 @@ const char *alias, GSList *groups) { GSList *buddies, *l; - GSList *pool = NULL; + PurpleAccount *account = purple_connection_get_account(js->gc); buddies = purple_find_buddies(js->gc->account, jid); @@ -117,25 +117,14 @@ groups = g_slist_delete_link(groups, l); } else { /* This buddy isn't in the group on the server anymore */ - pool = g_slist_prepend(pool, b); + purple_debug_info("jabber", "jabber_roster_parse(): Removing %s " + "from group '%s' on the local list\n", + purple_buddy_get_name(b), + purple_group_get_name(g)); + purple_blist_remove_buddy(b); } } - if (pool) { - GString *tmp = g_string_new(NULL); - GSList *list = pool; - for ( ; list; list = list->next) { - tmp = g_string_append(tmp, - purple_group_get_name(purple_buddy_get_group(list->data))); - if (list->next) - tmp = g_string_append(tmp, ", "); - } - - purple_debug_info("jabber", "jabber_roster_parse(): Removing %s from " - "groups: %s\n", jid, tmp->str); - g_string_free(tmp, TRUE); - } - if (groups) { char *tmp = roster_groups_join(groups); purple_debug_info("jabber", "jabber_roster_parse(): Adding %s to " @@ -145,17 +134,7 @@ while(groups) { PurpleGroup *g = purple_find_group(groups->data); - PurpleBuddy *b = NULL; - - /* If there are buddies we would otherwise delete, move them to - * the new group (instead of deleting them below) - */ - if (pool) { - b = pool->data; - pool = g_slist_delete_link(pool, pool); - } else { - b = purple_buddy_new(js->gc->account, jid, alias); - } + PurpleBuddy *b = purple_buddy_new(account, jid, alias); if(!g) { g = purple_group_new(groups->data); @@ -169,14 +148,6 @@ groups = g_slist_delete_link(groups, groups); } - /* Remove this person from all the groups they're no longer in on the - * server */ - while (pool) { - PurpleBuddy *b = pool->data; - purple_blist_remove_buddy(b); - pool = g_slist_delete_link(pool, pool); - } - g_slist_free(buddies); } @@ -229,7 +200,7 @@ 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;
--- a/libpurple/protocols/jabber/si.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/jabber/si.c Mon Nov 09 19:27:45 2009 +0000 @@ -1349,7 +1349,7 @@ jabber_ibb_session_close(jsx->ibb_session); } jabber_si_xfer_free(xfer); - purple_debug(PURPLE_DEBUG_INFO, "jabber", "in jabber_si_xfer_cancel_send\n"); + purple_debug_info("jabber", "in jabber_si_xfer_cancel_send\n"); } @@ -1381,7 +1381,7 @@ } jabber_si_xfer_free(xfer); - purple_debug(PURPLE_DEBUG_INFO, "jabber", "in jabber_si_xfer_request_denied\n"); + purple_debug_info("jabber", "in jabber_si_xfer_request_denied\n"); } @@ -1393,7 +1393,7 @@ jabber_ibb_session_close(jsx->ibb_session); } jabber_si_xfer_free(xfer); - purple_debug(PURPLE_DEBUG_INFO, "jabber", "in jabber_si_xfer_cancel_recv\n"); + purple_debug_info("jabber", "in jabber_si_xfer_cancel_recv\n"); }
--- a/libpurple/protocols/jabber/useravatar.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/jabber/useravatar.c Mon Nov 09 19:27:45 2009 +0000 @@ -262,7 +262,7 @@ gpointer icon_data; if(!url_text) { - purple_debug(PURPLE_DEBUG_ERROR, "jabber", + purple_debug_error("jabber", "do_buddy_avatar_update_fromurl got error \"%s\"", error_message); goto out;
--- a/libpurple/protocols/msn/contact.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/msn/contact.c Mon Nov 09 19:27:45 2009 +0000 @@ -206,6 +206,7 @@ "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 @@ 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 @@ g_free(type); g_free(member_id); g_free(invite); + g_free(display_text); } static void @@ -756,7 +765,7 @@ if (passport == NULL) continue; - if (!purple_email_is_valid(passport)) + if (!msn_email_is_valid(passport)) continue; if ((displayName = xmlnode_get_child(contactInfo, "displayName"))) @@ -1223,8 +1232,13 @@ if (user->invite_message) { char *tmp; body = g_markup_escape_text(user->invite_message, -1); - tmp = g_markup_escape_text(purple_connection_get_display_name(session->account->gc), -1); + + /* Ignore the cast, we treat it as const anyway. */ + tmp = (char *)purple_connection_get_display_name(session->account->gc); + tmp = tmp ? g_markup_escape_text(tmp, -1) : g_strdup(""); + invite = g_strdup_printf(MSN_CONTACT_INVITE_MESSAGE_XML, body, tmp); + g_free(body); g_free(tmp); @@ -1417,12 +1431,20 @@ 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 @@ g_return_if_reached(); } - - state = msn_callback_state_new(session); state->body = xmlnode_from_str(MSN_CONTACT_UPDATE_TEMPLATE, -1); @@ -1465,14 +1485,13 @@ 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);
--- a/libpurple/protocols/msn/msn.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/msn/msn.c Mon Nov 09 19:27:45 2009 +0000 @@ -118,6 +118,29 @@ return buf; } +gboolean +msn_email_is_valid(const char *passport) +{ + if (purple_email_is_valid(passport)) { + /* Special characters aren't allowed in domains, so only go to '@' */ + while (*passport != '@') { + if (*passport == '/') + return FALSE; + else if (*passport == '?') + return FALSE; + else if (*passport == '=') + return FALSE; + /* MSN also doesn't like colons, but that's checked already */ + + passport++; + } + + return TRUE; + } + + return FALSE; +} + static gboolean msn_send_attention(PurpleConnection *gc, const char *username, guint type) { @@ -611,9 +634,14 @@ MsnSession *session = gc->proto_data; if (session) { MsnUser *user = msn_userlist_find_user(session->userlist, who); - if (user) + if (user) { /* Include these too: MSN_CLIENT_CAP_MSNMOBILE|MSN_CLIENT_CAP_MSNDIRECT ? */ - ret = (user->clientid & MSN_CLIENT_CAP_WEBMSGR) == 0; + if ((user->clientid & MSN_CLIENT_CAP_WEBMSGR) || + user->networkid == MSN_NETWORK_YAHOO) + ret = FALSE; + else + ret = TRUE; + } } else ret = FALSE; } @@ -1511,7 +1539,7 @@ bname = purple_buddy_get_name(buddy); - if (!purple_email_is_valid(bname)) { + if (!msn_email_is_valid(bname)) { gchar *buf; buf = g_strdup_printf(_("Unable to add the buddy %s because the username is invalid. Usernames must be valid email addresses."), bname); if (!purple_conv_present_error(bname, purple_connection_get_account(gc), buf))
--- a/libpurple/protocols/msn/msn.h Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/msn/msn.h Mon Nov 09 19:27:45 2009 +0000 @@ -133,6 +133,7 @@ ((MSN_CLIENT_ID_VERSION << 24) | \ (MSN_CLIENT_ID_CAPABILITIES)) +gboolean msn_email_is_valid(const char *passport); void msn_act_id(PurpleConnection *gc, const char *entry); void msn_send_privacy(PurpleConnection *gc); void msn_send_im_message(MsnSession *session, MsnMessage *msg);
--- a/libpurple/protocols/msn/nexus.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/msn/nexus.c Mon Nov 09 19:27:45 2009 +0000 @@ -338,8 +338,10 @@ 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 @@ 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 @@ } g_free(ud); + g_free(key); } void
--- a/libpurple/protocols/msn/notification.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/msn/notification.c Mon Nov 09 19:27:45 2009 +0000 @@ -583,6 +583,7 @@ 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 @@ 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 @@ "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 @@ 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 @@ } static void -adl_241_error_cmd_post(MsnCmdProc *cmdproc, MsnCommand *cmd, char *payload, - size_t len) +rml_error_parse(MsnCmdProc *cmdproc, MsnCommand *cmd, char *payload, size_t len) { - /* khc: some googling suggests that error 241 means the buddy is somehow - in the local list, but not the server list, and that we should add - those buddies to the addressbook. For now I will just notify the user - about the raw payload, because I am lazy */ MsnSession *session; PurpleAccount *account; PurpleConnection *gc; - xmlnode *adl; - xmlnode *domain; - GString *emails; + char *adl, *reason; + int error = GPOINTER_TO_INT(cmd->payload_cbdata); session = cmdproc->session; account = session->account; gc = purple_account_get_connection(account); - adl = xmlnode_from_str(payload, len); - emails = g_string_new(NULL); + adl = g_strndup(payload, len); + reason = g_strdup_printf(_("Unknown error (%d): %s"), + error, adl); + g_free(adl); - domain = xmlnode_get_child(adl, "d"); - while (domain) { - const char *domain_str = xmlnode_get_attrib(domain, "n"); - xmlnode *contact = xmlnode_get_child(domain, "c"); - while (contact) { - g_string_append_printf(emails, "%s@%s\n", - xmlnode_get_attrib(contact, "n"), domain_str); - contact = xmlnode_get_next_twin(contact); - } - domain = xmlnode_get_next_twin(domain); - } - - purple_notify_error(gc, NULL, - _("The following users are missing from your addressbook"), emails->str); - g_string_free(emails, TRUE); - xmlnode_free(adl); + purple_notify_error(gc, NULL, _("Unable to remove user"), reason); + g_free(reason); } static void -adl_241_error_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +rml_error(MsnCmdProc *cmdproc, MsnTransaction *trans, int error) { - cmdproc->last_cmd->payload_cb = adl_241_error_cmd_post; - cmd->payload_len = atoi(cmd->params[1]); + MsnSession *session; + PurpleAccount *account; + PurpleConnection *gc; + MsnCommand *cmd = cmdproc->last_cmd; + + session = cmdproc->session; + account = session->account; + gc = purple_account_get_connection(account); + + purple_debug_error("msn", "RML error\n"); + if (cmd->param_count > 1) { + cmd->payload_cb = rml_error_parse; + cmd->payload_len = atoi(cmd->params[1]); + cmd->payload_cbdata = GINT_TO_POINTER(error); + } else { + char *reason = g_strdup_printf(_("Unknown error (%d)"), error); + purple_notify_error(gc, NULL, _("Unable to remove user"), reason); + g_free(reason); + } } static void @@ -962,9 +993,8 @@ 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 @@ /* 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 @@ } 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 @@ 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_table_add_cmd(cbs_table, "fallback", "XFR", xfr_cmd); - msn_table_add_cmd(cbs_table, NULL, "241", adl_241_error_cmd); - msn_table_add_error(cbs_table, "ADL", adl_error); + msn_table_add_error(cbs_table, "RML", rml_error); msn_table_add_error(cbs_table, "FQY", fqy_error); msn_table_add_error(cbs_table, "USR", usr_error);
--- a/libpurple/protocols/msn/oim.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/msn/oim.c Mon Nov 09 19:27:45 2009 +0000 @@ -153,7 +153,7 @@ gpointer cb_data; } MsnOimRequestData; -static void msn_oim_request_helper(MsnOimRequestData *data); +static gboolean msn_oim_request_helper(MsnOimRequestData *data); static void msn_oim_request_cb(MsnSoapMessage *request, MsnSoapMessage *response, @@ -202,7 +202,7 @@ g_free(data); } -static void +static gboolean msn_oim_request_helper(MsnOimRequestData *data) { MsnSession *session = data->oim->session; @@ -224,13 +224,13 @@ const char *msn_p; token = msn_nexus_get_token(session->nexus, MSN_AUTH_MESSENGER_WEB); - g_return_if_fail(token != NULL); + g_return_val_if_fail(token != NULL, FALSE); msn_t = g_hash_table_lookup(token, "t"); msn_p = g_hash_table_lookup(token, "p"); - g_return_if_fail(msn_t != NULL); - g_return_if_fail(msn_p != NULL); + g_return_val_if_fail(msn_t != NULL, FALSE); + g_return_val_if_fail(msn_p != NULL, FALSE); passport = xmlnode_get_child(data->body, "Header/PassportCookie"); xml_t = xmlnode_get_child(passport, "t"); @@ -248,6 +248,8 @@ msn_soap_message_new(data->action, xmlnode_copy(data->body)), data->host, data->url, FALSE, msn_oim_request_cb, data); + + return FALSE; } @@ -373,6 +375,7 @@ 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 @@ 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 @@ } } } + + if (msg) + msn_oim_free_send_req(msg); } void
--- a/libpurple/protocols/msn/servconn.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/msn/servconn.c Mon Nov 09 19:27:45 2009 +0000 @@ -86,7 +86,7 @@ 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 @@ 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 @@ static gboolean servconn_idle_timeout_cb(MsnServConn *servconn) { + servconn->timeout_handle = 0; msn_servconn_disconnect(servconn); - servconn->timeout_handle = 0; return FALSE; } @@ -308,7 +308,7 @@ 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 @@ 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 @@ servconn->processing = FALSE; - if (servconn->wasted) + if (servconn->wasted) { msn_servconn_destroy(servconn); + servconn = NULL; + } g_free(old_rx_buf); + return servconn; } #if 0
--- a/libpurple/protocols/msn/servconn.h Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/msn/servconn.h Mon Nov 09 19:27:45 2009 +0000 @@ -178,8 +178,10 @@ * 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
--- a/libpurple/protocols/msn/transaction.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/msn/transaction.c Mon Nov 09 19:27:45 2009 +0000 @@ -59,6 +59,9 @@ 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 @@ 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)
--- a/libpurple/protocols/msn/transaction.h Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/msn/transaction.h Mon Nov 09 19:27:45 2009 +0000 @@ -48,6 +48,8 @@ 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_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);
--- a/libpurple/protocols/msn/user.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/msn/user.c Mon Nov 09 19:27:45 2009 +0000 @@ -183,12 +183,15 @@ { 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; }
--- a/libpurple/protocols/msn/userlist.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/msn/userlist.c Mon Nov 09 19:27:45 2009 +0000 @@ -539,7 +539,7 @@ purple_debug_info("msn", "Add user: %s to group: %s\n", who, new_group_name); - if (!purple_email_is_valid(who)) + if (!msn_email_is_valid(who)) { /* only notify the user about problems adding to the friends list * maybe we should do something else for other lists, but it probably
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/Makefile.am Mon Nov 09 19:27:45 2009 +0000 @@ -0,0 +1,63 @@ +EXTRA_DIST = \ + Makefile.mingw + +pkgdir = $(libdir)/purple-$(PURPLE_MAJOR_VERSION) + +MXITSOURCES = \ + actions.c \ + actions.h \ + aes.c \ + aes.h \ + chunk.c \ + chunk.h \ + cipher.c \ + cipher.h \ + filexfer.c \ + filexfer.h \ + formcmds.c \ + formcmds.h \ + http.c \ + http.h \ + login.c \ + login.h \ + markup.c \ + markup.h \ + multimx.c \ + multimx.h \ + mxit.c \ + mxit.h \ + profile.c \ + profile.h \ + protocol.c \ + protocol.h \ + roster.c \ + roster.h \ + splashscreen.c \ + splashscreen.h + + +AM_CFLAGS = $(st) + +libmxit_la_LDFLAGS = -module -avoid-version + +if STATIC_MXIT + +st = -DPURPLE_STATIC_PRPL +noinst_LTLIBRARIES = libmxit.la +libmxit_la_SOURCES = $(MXITSOURCES) +libmxit_la_CFLAGS = $(AM_CFLAGS) + +else + +st = +pkg_LTLIBRARIES = libmxit.la +libmxit_la_SOURCES = $(MXITSOURCES) +libmxit_la_LIBADD = $(GLIB_LIBS) + +endif + +AM_CPPFLAGS = \ + -I$(top_srcdir)/libpurple \ + -I$(top_builddir)/libpurple \ + $(GLIB_CFLAGS) \ + $(DEBUG_CFLAGS)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/Makefile.mingw Mon Nov 09 19:27:45 2009 +0000 @@ -0,0 +1,91 @@ +# +# Makefile.mingw +# +# Description: Makefile for win32 (mingw) version of libmxit +# + +PIDGIN_TREE_TOP := ../../.. +include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak + +TARGET = libmxit +TYPE = PLUGIN + +# Static or Plugin... +ifeq ($(TYPE),STATIC) + DEFINES += -DSTATIC + DLL_INSTALL_DIR = $(PURPLE_INSTALL_DIR) +else +ifeq ($(TYPE),PLUGIN) + DLL_INSTALL_DIR = $(PURPLE_INSTALL_PLUGINS_DIR) +endif +endif + +## +## INCLUDE PATHS +## +INCLUDE_PATHS += -I. \ + -I$(GTK_TOP)/include \ + -I$(GTK_TOP)/include/glib-2.0 \ + -I$(GTK_TOP)/lib/glib-2.0/include \ + -I$(PURPLE_TOP) \ + -I$(PURPLE_TOP)/win32 \ + -I$(PIDGIN_TREE_TOP) + +LIB_PATHS += -L$(GTK_TOP)/lib \ + -L$(PURPLE_TOP) + +## +## SOURCES, OBJECTS +## +C_SRC = actions.c \ + aes.c \ + chunk.c \ + cipher.c \ + filexfer.c \ + formcmds.c \ + http.c \ + login.c \ + markup.c \ + multimx.c \ + mxit.c \ + profile.c \ + protocol.c \ + roster.c \ + splashscreen.c + +OBJECTS = $(C_SRC:%.c=%.o) + +## +## LIBRARIES +## +LIBS = \ + -lglib-2.0 \ + -lintl \ + -lws2_32 \ + -lpurple + +include $(PIDGIN_COMMON_RULES) + +## +## TARGET DEFINITIONS +## +.PHONY: all install clean + +all: $(TARGET).dll + +install: all $(DLL_INSTALL_DIR) + cp $(TARGET).dll $(DLL_INSTALL_DIR) + +$(OBJECTS): $(PURPLE_CONFIG_H) + +$(TARGET).dll: $(PURPLE_DLL).a $(OBJECTS) + $(CC) -shared $(OBJECTS) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -o $(TARGET).dll + +## +## CLEAN RULES +## +clean: + rm -f $(OBJECTS) + rm -f $(TARGET).dll + +include $(PIDGIN_COMMON_TARGETS)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/actions.c Mon Nov 09 19:27:45 2009 +0000 @@ -0,0 +1,437 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- handle MXit plugin actions -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include "purple.h" + +#include "protocol.h" +#include "mxit.h" +#include "roster.h" +#include "actions.h" +#include "splashscreen.h" +#include "cipher.h" +#include "profile.h" + + +/* MXit Moods */ +static const char* moods[] = { + /* 0 */ "None", + /* 1 */ "Angry", + /* 2 */ "Excited", + /* 3 */ "Grumpy", + /* 4 */ "Happy", + /* 5 */ "In Love", + /* 6 */ "Invincible", + /* 7 */ "Sad", + /* 8 */ "Hot", + /* 9 */ "Sick", + /* 10 */ "Sleepy" +}; + + +/*------------------------------------------------------------------------ + * The user has selected to change their current mood. + * + * @param gc The connection object + * @param fields The fields from the request pop-up + */ +static void mxit_cb_set_mood( PurpleConnection* gc, PurpleRequestFields* fields ) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + int mood = purple_request_fields_get_choice( fields, "mood" ); + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_set_mood (%i)\n", mood ); + + if ( !PURPLE_CONNECTION_IS_VALID( gc ) ) { + purple_debug_error( MXIT_PLUGIN_ID, "Unable to set mood; account offline.\n" ); + return; + } + + /* Save the new mood in session */ + session->mood = mood; + + /* now send the update to MXit */ + mxit_send_mood( session, mood ); +} + + +/*------------------------------------------------------------------------ + * Create and display the mood selection window to the user. + * + * @param action The action object + */ +static void mxit_cb_action_mood( PurplePluginAction* action ) +{ + PurpleConnection* gc = (PurpleConnection*) action->context; + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + + PurpleRequestFields* fields = NULL; + PurpleRequestFieldGroup* group = NULL; + PurpleRequestField* field = NULL; + unsigned int i = 0; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_action_mood\n" ); + + fields = purple_request_fields_new(); + group = purple_request_field_group_new( NULL ); + purple_request_fields_add_group( fields, group ); + + /* show current mood */ + field = purple_request_field_string_new( "current", _( "Current Mood" ), _( moods[session->mood] ), FALSE ); + purple_request_field_string_set_editable( field, FALSE ); /* current mood field is not editable */ + purple_request_field_group_add_field( group, field ); + + /* add all moods to list */ + field = purple_request_field_choice_new( "mood", _( "New Mood" ), 0 ); + for ( i = 0; i < ARRAY_SIZE( moods ); i++ ) { + purple_request_field_choice_add( field, _( moods[i] ) ); + } + purple_request_field_set_required( field, TRUE ); + purple_request_field_choice_set_default_value( field, session->mood ); + purple_request_field_group_add_field( group, field ); + + /* (reference: "libpurple/request.h") */ + purple_request_fields( gc, _( "Mood" ), _( "Change your Mood" ), _( "How do you feel right now?" ), fields, _( "Set" ), + G_CALLBACK( mxit_cb_set_mood ), _( "Cancel" ), NULL, purple_connection_get_account( gc ), NULL, NULL, gc ); +} + + +/*------------------------------------------------------------------------ + * The user has selected to change their profile. + * + * @param gc The connection object + * @param fields The fields from the request pop-up + */ +static void mxit_cb_set_profile( PurpleConnection* gc, PurpleRequestFields* fields ) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + PurpleRequestField* field = NULL; + const char* pin = NULL; + const char* pin2 = NULL; + const char* name = NULL; + const char* bday = NULL; + const char* err = NULL; + int len; + int i; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_set_profile\n" ); + + if ( !PURPLE_CONNECTION_IS_VALID( gc ) ) { + purple_debug_error( MXIT_PLUGIN_ID, "Unable to update profile; account offline.\n" ); + return; + } + + /* validate pin */ + pin = purple_request_fields_get_string( fields, "pin" ); + if ( !pin ) { + err = "The PIN you entered is invalid."; + goto out; + } + len = strlen( pin ); + if ( ( len < 4 ) || ( len > 10 ) ) { + err = "The PIN you entered has an invalid length [4-10]."; + goto out; + } + for ( i = 0; i < len; i++ ) { + if ( !g_ascii_isdigit( pin[i] ) ) { + err = "The PIN is invalid. It should only consist of digits [0-9]."; + goto out; + } + } + pin2 = purple_request_fields_get_string( fields, "pin2" ); + if ( ( !pin2 ) || ( strcmp( pin, pin2 ) != 0 ) ) { + err = "The two PINs you entered does not match."; + goto out; + } + + /* validate name */ + name = purple_request_fields_get_string( fields, "name" ); + if ( ( !name ) || ( strlen( name ) < 3 ) ) { + err = "The name you entered is invalid."; + goto out; + } + + /* validate birthdate */ + bday = purple_request_fields_get_string( fields, "bday" ); + if ( ( !bday ) || ( strlen( bday ) < 10 ) || ( !validateDate( bday ) ) ) { + err = "The birthday you entered is invalid. The correct format is: 'YYYY-MM-DD'."; + goto out; + } + +out: + if ( !err ) { + struct MXitProfile* profile = session->profile; + GString* attributes = g_string_sized_new( 128 ); + char attrib[512]; + unsigned int acount = 0; + + /* all good, so we can now update the profile */ + + /* update pin */ + purple_account_set_password( session->acc, pin ); + g_free( session->encpwd ); + session->encpwd = mxit_encrypt_password( session ); + + /* update name */ + g_strlcpy( profile->nickname, name, sizeof( profile->nickname ) ); + g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_FULLNAME, CP_PROF_TYPE_UTF8, profile->nickname ); + g_string_append( attributes, attrib ); + acount++; + + /* update hidden */ + field = purple_request_fields_get_field( fields, "hidden" ); + profile->hidden = purple_request_field_bool_get_value( field ); + g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_HIDENUMBER, CP_PROF_TYPE_BOOL, ( profile->hidden ) ? "1" : "0" ); + g_string_append( attributes, attrib ); + acount++; + + /* update birthday */ + strcpy( profile->birthday, bday ); + g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_BIRTHDATE, CP_PROF_TYPE_UTF8, profile->birthday ); + g_string_append( attributes, attrib ); + acount++; + + /* update gender */ + if ( purple_request_fields_get_choice( fields, "male" ) == 0 ) + profile->male = FALSE; + else + profile->male = TRUE; + g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_GENDER, CP_PROF_TYPE_BOOL, ( profile->male ) ? "1" : "0" ); + g_string_append( attributes, attrib ); + acount++; + + /* update title */ + name = purple_request_fields_get_string( fields, "title" ); + if ( !name ) + profile->title[0] = '\0'; + else + strcpy( profile->title, name ); + g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_TITLE, CP_PROF_TYPE_UTF8, profile->title ); + g_string_append( attributes, attrib ); + acount++; + + /* update firstname */ + name = purple_request_fields_get_string( fields, "firstname" ); + if ( !name ) + profile->firstname[0] = '\0'; + else + strcpy( profile->firstname, name ); + g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_FIRSTNAME, CP_PROF_TYPE_UTF8, profile->firstname ); + g_string_append( attributes, attrib ); + acount++; + + /* update lastname */ + name = purple_request_fields_get_string( fields, "lastname" ); + if ( !name ) + profile->lastname[0] = '\0'; + else + strcpy( profile->lastname, name ); + g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_LASTNAME, CP_PROF_TYPE_UTF8, profile->lastname ); + g_string_append( attributes, attrib ); + acount++; + + /* update email address */ + name = purple_request_fields_get_string( fields, "email" ); + if ( !name ) + profile->email[0] = '\0'; + else + strcpy( profile->email, name ); + g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_EMAIL, CP_PROF_TYPE_UTF8, profile->email ); + g_string_append( attributes, attrib ); + acount++; + + /* update mobile number */ + name = purple_request_fields_get_string( fields, "mobilenumber" ); + if ( !name ) + profile->mobilenr[0] = '\0'; + else + strcpy( profile->mobilenr, name ); + g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_MOBILENR, CP_PROF_TYPE_UTF8, profile->mobilenr ); + g_string_append( attributes, attrib ); + acount++; + + /* send the profile update to MXit */ + mxit_send_extprofile_update( session, session->encpwd, acount, attributes->str ); + g_string_free( attributes, TRUE ); + } + else { + /* show error to user */ + mxit_popup( PURPLE_NOTIFY_MSG_ERROR, _( "Profile Update Error" ), _( err ) ); + } +} + + +/*------------------------------------------------------------------------ + * Display and update the user's profile. + * + * @param action The action object + */ +static void mxit_cb_action_profile( PurplePluginAction* action ) +{ + PurpleConnection* gc = (PurpleConnection*) action->context; + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + struct MXitProfile* profile = session->profile; + + PurpleRequestFields* fields = NULL; + PurpleRequestFieldGroup* group = NULL; + PurpleRequestField* field = NULL; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_action_profile\n" ); + + /* ensure that we actually have the user's profile information */ + if ( !profile ) { + /* no profile information yet, so we cannot update */ + mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Profile" ), _( "Your profile information is not yet retrieved. Please try again later." ) ); + return; + } + + fields = purple_request_fields_new(); + group = purple_request_field_group_new( NULL ); + purple_request_fields_add_group( fields, group ); + + /* pin */ + field = purple_request_field_string_new( "pin", _( "PIN" ), session->acc->password, FALSE ); + purple_request_field_string_set_masked( field, TRUE ); + purple_request_field_group_add_field( group, field ); + field = purple_request_field_string_new( "pin2", _( "Verify PIN" ), session->acc->password, FALSE ); + purple_request_field_string_set_masked( field, TRUE ); + purple_request_field_group_add_field( group, field ); + + /* display name */ + field = purple_request_field_string_new( "name", _( "Display Name" ), profile->nickname, FALSE ); + purple_request_field_group_add_field( group, field ); + + /* birthday */ + field = purple_request_field_string_new( "bday", _( "Birthday" ), profile->birthday, FALSE ); + purple_request_field_group_add_field( group, field ); + + /* gender */ + field = purple_request_field_choice_new( "male", _( "Gender" ), ( profile->male ) ? 1 : 0 ); + purple_request_field_choice_add( field, _( "Female" ) ); /* 0 */ + purple_request_field_choice_add( field, _( "Male" ) ); /* 1 */ + purple_request_field_group_add_field( group, field ); + + /* hidden */ + field = purple_request_field_bool_new( "hidden", _( "Hide my number" ), profile->hidden ); + purple_request_field_group_add_field( group, field ); + + /* title */ + field = purple_request_field_string_new( "title", _( "Title" ), profile->title, FALSE ); + purple_request_field_group_add_field( group, field ); + + /* first name */ + field = purple_request_field_string_new( "firstname", _( "First Name" ), profile->firstname, FALSE ); + purple_request_field_group_add_field( group, field ); + + /* last name */ + field = purple_request_field_string_new( "lastname", _( "Last Name" ), profile->lastname, FALSE ); + purple_request_field_group_add_field( group, field ); + + /* email */ + field = purple_request_field_string_new( "email", _( "Email" ), profile->email, FALSE ); + purple_request_field_group_add_field( group, field ); + + /* mobile number */ + field = purple_request_field_string_new( "mobilenumber", _( "Mobile Number" ), profile->mobilenr, FALSE ); + purple_request_field_group_add_field( group, field ); + + /* (reference: "libpurple/request.h") */ + purple_request_fields( gc, _( "Profile" ), _( "Update your Profile" ), _( "Here you can update your MXit profile" ), fields, _( "Set" ), + G_CALLBACK( mxit_cb_set_profile ), _( "Cancel" ), NULL, purple_connection_get_account( gc ), NULL, NULL, gc ); +} + + +/*------------------------------------------------------------------------ + * Display the current splash-screen, or a notification pop-up if one is not available. + * + * @param action The action object + */ +static void mxit_cb_action_splash( PurplePluginAction* action ) +{ + PurpleConnection* gc = (PurpleConnection*) action->context; + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + + if ( splash_current( session ) != NULL ) + splash_display( session ); + else + mxit_popup( PURPLE_NOTIFY_MSG_INFO, _( "View Splash" ), _( "There is no splash-screen currently available" ) ); +} + + +/*------------------------------------------------------------------------ + * Display info about the plugin. + * + * @param action The action object + */ +static void mxit_cb_action_about( PurplePluginAction* action ) +{ + char version[256]; + + g_snprintf( version, sizeof( version ), "MXit libPurple Plugin v%s\n" + "MXit Client Protocol v%s\n\n" + "Author:\nPieter Loubser\n\n" + "Contributors:\nAndrew Victor\n\n" + "Testers:\nBraeme Le Roux\n\n", + MXIT_PLUGIN_VERSION, MXIT_CP_RELEASE ); + + mxit_popup( PURPLE_NOTIFY_MSG_INFO, _( "About" ), version ); +} + + +/*------------------------------------------------------------------------ + * Associate actions with the MXit plugin. + * + * @param plugin The MXit protocol plugin + * @param context The connection context (if available) + * @return The list of plugin actions + */ +GList* mxit_actions( PurplePlugin* plugin, gpointer context ) +{ + PurplePluginAction* action = NULL; + GList* m = NULL; + + /* display / change mood */ + action = purple_plugin_action_new( _( "Change Mood..." ), mxit_cb_action_mood ); + m = g_list_append( m, action ); + + /* display / change profile */ + action = purple_plugin_action_new( _( "Change Profile..." ), mxit_cb_action_profile ); + m = g_list_append( m, action ); + + /* display splash-screen */ + action = purple_plugin_action_new( _( "View Splash..." ), mxit_cb_action_splash ); + m = g_list_append( m, action ); + + /* display plugin version */ + action = purple_plugin_action_new( _( "About..." ), mxit_cb_action_about ); + m = g_list_append( m, action ); + + return m; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/actions.h Mon Nov 09 19:27:45 2009 +0000 @@ -0,0 +1,34 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- handle MXit plugin actions -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef _MXIT_ACTIONS_H_ +#define _MXIT_ACTIONS_H_ + + +/* callbacks */ +GList* mxit_actions( PurplePlugin* plugin, gpointer context ); + + +#endif /* _MXIT_ACTIONS_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/aes.c Mon Nov 09 19:27:45 2009 +0000 @@ -0,0 +1,405 @@ + +// advanced encryption standard +// author: karl malbrain, malbrain@yahoo.com + +/* +This work, including the source code, documentation +and related data, is placed into the public domain. + +The orginal author is Karl Malbrain. + +THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY +OF ANY KIND, NOT EVEN THE IMPLIED WARRANTY OF +MERCHANTABILITY. THE AUTHOR OF THIS SOFTWARE, +ASSUMES _NO_ RESPONSIBILITY FOR ANY CONSEQUENCE +RESULTING FROM THE USE, MODIFICATION, OR +REDISTRIBUTION OF THIS SOFTWARE. +*/ + +#include <string.h> +#include <memory.h> + +#include "aes.h" + +// AES only supports Nb=4 +#define Nb 4 // number of columns in the state & expanded key + +#define Nk 4 // number of columns in a key +#define Nr 10 // number of rounds in encryption + +static uchar Sbox[256] = { // forward s-box +0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, +0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, +0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, +0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, +0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, +0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, +0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, +0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, +0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, +0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, +0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, +0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, +0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, +0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, +0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, +0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16}; + +static uchar InvSbox[256] = { // inverse s-box +0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, +0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, +0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, +0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, +0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, +0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, +0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, +0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, +0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, +0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, +0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, +0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, +0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, +0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, +0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, +0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d}; + +// combined Xtimes2[Sbox[]] +static uchar Xtime2Sbox[256] = { +0xc6, 0xf8, 0xee, 0xf6, 0xff, 0xd6, 0xde, 0x91, 0x60, 0x02, 0xce, 0x56, 0xe7, 0xb5, 0x4d, 0xec, +0x8f, 0x1f, 0x89, 0xfa, 0xef, 0xb2, 0x8e, 0xfb, 0x41, 0xb3, 0x5f, 0x45, 0x23, 0x53, 0xe4, 0x9b, +0x75, 0xe1, 0x3d, 0x4c, 0x6c, 0x7e, 0xf5, 0x83, 0x68, 0x51, 0xd1, 0xf9, 0xe2, 0xab, 0x62, 0x2a, +0x08, 0x95, 0x46, 0x9d, 0x30, 0x37, 0x0a, 0x2f, 0x0e, 0x24, 0x1b, 0xdf, 0xcd, 0x4e, 0x7f, 0xea, +0x12, 0x1d, 0x58, 0x34, 0x36, 0xdc, 0xb4, 0x5b, 0xa4, 0x76, 0xb7, 0x7d, 0x52, 0xdd, 0x5e, 0x13, +0xa6, 0xb9, 0x00, 0xc1, 0x40, 0xe3, 0x79, 0xb6, 0xd4, 0x8d, 0x67, 0x72, 0x94, 0x98, 0xb0, 0x85, +0xbb, 0xc5, 0x4f, 0xed, 0x86, 0x9a, 0x66, 0x11, 0x8a, 0xe9, 0x04, 0xfe, 0xa0, 0x78, 0x25, 0x4b, +0xa2, 0x5d, 0x80, 0x05, 0x3f, 0x21, 0x70, 0xf1, 0x63, 0x77, 0xaf, 0x42, 0x20, 0xe5, 0xfd, 0xbf, +0x81, 0x18, 0x26, 0xc3, 0xbe, 0x35, 0x88, 0x2e, 0x93, 0x55, 0xfc, 0x7a, 0xc8, 0xba, 0x32, 0xe6, +0xc0, 0x19, 0x9e, 0xa3, 0x44, 0x54, 0x3b, 0x0b, 0x8c, 0xc7, 0x6b, 0x28, 0xa7, 0xbc, 0x16, 0xad, +0xdb, 0x64, 0x74, 0x14, 0x92, 0x0c, 0x48, 0xb8, 0x9f, 0xbd, 0x43, 0xc4, 0x39, 0x31, 0xd3, 0xf2, +0xd5, 0x8b, 0x6e, 0xda, 0x01, 0xb1, 0x9c, 0x49, 0xd8, 0xac, 0xf3, 0xcf, 0xca, 0xf4, 0x47, 0x10, +0x6f, 0xf0, 0x4a, 0x5c, 0x38, 0x57, 0x73, 0x97, 0xcb, 0xa1, 0xe8, 0x3e, 0x96, 0x61, 0x0d, 0x0f, +0xe0, 0x7c, 0x71, 0xcc, 0x90, 0x06, 0xf7, 0x1c, 0xc2, 0x6a, 0xae, 0x69, 0x17, 0x99, 0x3a, 0x27, +0xd9, 0xeb, 0x2b, 0x22, 0xd2, 0xa9, 0x07, 0x33, 0x2d, 0x3c, 0x15, 0xc9, 0x87, 0xaa, 0x50, 0xa5, +0x03, 0x59, 0x09, 0x1a, 0x65, 0xd7, 0x84, 0xd0, 0x82, 0x29, 0x5a, 0x1e, 0x7b, 0xa8, 0x6d, 0x2c +}; + +// combined Xtimes3[Sbox[]] +static uchar Xtime3Sbox[256] = { +0xa5, 0x84, 0x99, 0x8d, 0x0d, 0xbd, 0xb1, 0x54, 0x50, 0x03, 0xa9, 0x7d, 0x19, 0x62, 0xe6, 0x9a, +0x45, 0x9d, 0x40, 0x87, 0x15, 0xeb, 0xc9, 0x0b, 0xec, 0x67, 0xfd, 0xea, 0xbf, 0xf7, 0x96, 0x5b, +0xc2, 0x1c, 0xae, 0x6a, 0x5a, 0x41, 0x02, 0x4f, 0x5c, 0xf4, 0x34, 0x08, 0x93, 0x73, 0x53, 0x3f, +0x0c, 0x52, 0x65, 0x5e, 0x28, 0xa1, 0x0f, 0xb5, 0x09, 0x36, 0x9b, 0x3d, 0x26, 0x69, 0xcd, 0x9f, +0x1b, 0x9e, 0x74, 0x2e, 0x2d, 0xb2, 0xee, 0xfb, 0xf6, 0x4d, 0x61, 0xce, 0x7b, 0x3e, 0x71, 0x97, +0xf5, 0x68, 0x00, 0x2c, 0x60, 0x1f, 0xc8, 0xed, 0xbe, 0x46, 0xd9, 0x4b, 0xde, 0xd4, 0xe8, 0x4a, +0x6b, 0x2a, 0xe5, 0x16, 0xc5, 0xd7, 0x55, 0x94, 0xcf, 0x10, 0x06, 0x81, 0xf0, 0x44, 0xba, 0xe3, +0xf3, 0xfe, 0xc0, 0x8a, 0xad, 0xbc, 0x48, 0x04, 0xdf, 0xc1, 0x75, 0x63, 0x30, 0x1a, 0x0e, 0x6d, +0x4c, 0x14, 0x35, 0x2f, 0xe1, 0xa2, 0xcc, 0x39, 0x57, 0xf2, 0x82, 0x47, 0xac, 0xe7, 0x2b, 0x95, +0xa0, 0x98, 0xd1, 0x7f, 0x66, 0x7e, 0xab, 0x83, 0xca, 0x29, 0xd3, 0x3c, 0x79, 0xe2, 0x1d, 0x76, +0x3b, 0x56, 0x4e, 0x1e, 0xdb, 0x0a, 0x6c, 0xe4, 0x5d, 0x6e, 0xef, 0xa6, 0xa8, 0xa4, 0x37, 0x8b, +0x32, 0x43, 0x59, 0xb7, 0x8c, 0x64, 0xd2, 0xe0, 0xb4, 0xfa, 0x07, 0x25, 0xaf, 0x8e, 0xe9, 0x18, +0xd5, 0x88, 0x6f, 0x72, 0x24, 0xf1, 0xc7, 0x51, 0x23, 0x7c, 0x9c, 0x21, 0xdd, 0xdc, 0x86, 0x85, +0x90, 0x42, 0xc4, 0xaa, 0xd8, 0x05, 0x01, 0x12, 0xa3, 0x5f, 0xf9, 0xd0, 0x91, 0x58, 0x27, 0xb9, +0x38, 0x13, 0xb3, 0x33, 0xbb, 0x70, 0x89, 0xa7, 0xb6, 0x22, 0x92, 0x20, 0x49, 0xff, 0x78, 0x7a, +0x8f, 0xf8, 0x80, 0x17, 0xda, 0x31, 0xc6, 0xb8, 0xc3, 0xb0, 0x77, 0x11, 0xcb, 0xfc, 0xd6, 0x3a +}; + +// modular multiplication tables +// based on: + +// Xtime2[x] = (x & 0x80 ? 0x1b : 0) ^ (x + x) +// Xtime3[x] = x^Xtime2[x]; + +#if 0 +static uchar Xtime2[256] = { +0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e, +0x20, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2c, 0x2e, 0x30, 0x32, 0x34, 0x36, 0x38, 0x3a, 0x3c, 0x3e, +0x40, 0x42, 0x44, 0x46, 0x48, 0x4a, 0x4c, 0x4e, 0x50, 0x52, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e, +0x60, 0x62, 0x64, 0x66, 0x68, 0x6a, 0x6c, 0x6e, 0x70, 0x72, 0x74, 0x76, 0x78, 0x7a, 0x7c, 0x7e, +0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c, 0x8e, 0x90, 0x92, 0x94, 0x96, 0x98, 0x9a, 0x9c, 0x9e, +0xa0, 0xa2, 0xa4, 0xa6, 0xa8, 0xaa, 0xac, 0xae, 0xb0, 0xb2, 0xb4, 0xb6, 0xb8, 0xba, 0xbc, 0xbe, +0xc0, 0xc2, 0xc4, 0xc6, 0xc8, 0xca, 0xcc, 0xce, 0xd0, 0xd2, 0xd4, 0xd6, 0xd8, 0xda, 0xdc, 0xde, +0xe0, 0xe2, 0xe4, 0xe6, 0xe8, 0xea, 0xec, 0xee, 0xf0, 0xf2, 0xf4, 0xf6, 0xf8, 0xfa, 0xfc, 0xfe, +0x1b, 0x19, 0x1f, 0x1d, 0x13, 0x11, 0x17, 0x15, 0x0b, 0x09, 0x0f, 0x0d, 0x03, 0x01, 0x07, 0x05, +0x3b, 0x39, 0x3f, 0x3d, 0x33, 0x31, 0x37, 0x35, 0x2b, 0x29, 0x2f, 0x2d, 0x23, 0x21, 0x27, 0x25, +0x5b, 0x59, 0x5f, 0x5d, 0x53, 0x51, 0x57, 0x55, 0x4b, 0x49, 0x4f, 0x4d, 0x43, 0x41, 0x47, 0x45, +0x7b, 0x79, 0x7f, 0x7d, 0x73, 0x71, 0x77, 0x75, 0x6b, 0x69, 0x6f, 0x6d, 0x63, 0x61, 0x67, 0x65, +0x9b, 0x99, 0x9f, 0x9d, 0x93, 0x91, 0x97, 0x95, 0x8b, 0x89, 0x8f, 0x8d, 0x83, 0x81, 0x87, 0x85, +0xbb, 0xb9, 0xbf, 0xbd, 0xb3, 0xb1, 0xb7, 0xb5, 0xab, 0xa9, 0xaf, 0xad, 0xa3, 0xa1, 0xa7, 0xa5, +0xdb, 0xd9, 0xdf, 0xdd, 0xd3, 0xd1, 0xd7, 0xd5, 0xcb, 0xc9, 0xcf, 0xcd, 0xc3, 0xc1, 0xc7, 0xc5, +0xfb, 0xf9, 0xff, 0xfd, 0xf3, 0xf1, 0xf7, 0xf5, 0xeb, 0xe9, 0xef, 0xed, 0xe3, 0xe1, 0xe7, 0xe5}; +#endif + +static uchar Xtime9[256] = { +0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77, +0x90, 0x99, 0x82, 0x8b, 0xb4, 0xbd, 0xa6, 0xaf, 0xd8, 0xd1, 0xca, 0xc3, 0xfc, 0xf5, 0xee, 0xe7, +0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04, 0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c, +0xab, 0xa2, 0xb9, 0xb0, 0x8f, 0x86, 0x9d, 0x94, 0xe3, 0xea, 0xf1, 0xf8, 0xc7, 0xce, 0xd5, 0xdc, +0x76, 0x7f, 0x64, 0x6d, 0x52, 0x5b, 0x40, 0x49, 0x3e, 0x37, 0x2c, 0x25, 0x1a, 0x13, 0x08, 0x01, +0xe6, 0xef, 0xf4, 0xfd, 0xc2, 0xcb, 0xd0, 0xd9, 0xae, 0xa7, 0xbc, 0xb5, 0x8a, 0x83, 0x98, 0x91, +0x4d, 0x44, 0x5f, 0x56, 0x69, 0x60, 0x7b, 0x72, 0x05, 0x0c, 0x17, 0x1e, 0x21, 0x28, 0x33, 0x3a, +0xdd, 0xd4, 0xcf, 0xc6, 0xf9, 0xf0, 0xeb, 0xe2, 0x95, 0x9c, 0x87, 0x8e, 0xb1, 0xb8, 0xa3, 0xaa, +0xec, 0xe5, 0xfe, 0xf7, 0xc8, 0xc1, 0xda, 0xd3, 0xa4, 0xad, 0xb6, 0xbf, 0x80, 0x89, 0x92, 0x9b, +0x7c, 0x75, 0x6e, 0x67, 0x58, 0x51, 0x4a, 0x43, 0x34, 0x3d, 0x26, 0x2f, 0x10, 0x19, 0x02, 0x0b, +0xd7, 0xde, 0xc5, 0xcc, 0xf3, 0xfa, 0xe1, 0xe8, 0x9f, 0x96, 0x8d, 0x84, 0xbb, 0xb2, 0xa9, 0xa0, +0x47, 0x4e, 0x55, 0x5c, 0x63, 0x6a, 0x71, 0x78, 0x0f, 0x06, 0x1d, 0x14, 0x2b, 0x22, 0x39, 0x30, +0x9a, 0x93, 0x88, 0x81, 0xbe, 0xb7, 0xac, 0xa5, 0xd2, 0xdb, 0xc0, 0xc9, 0xf6, 0xff, 0xe4, 0xed, +0x0a, 0x03, 0x18, 0x11, 0x2e, 0x27, 0x3c, 0x35, 0x42, 0x4b, 0x50, 0x59, 0x66, 0x6f, 0x74, 0x7d, +0xa1, 0xa8, 0xb3, 0xba, 0x85, 0x8c, 0x97, 0x9e, 0xe9, 0xe0, 0xfb, 0xf2, 0xcd, 0xc4, 0xdf, 0xd6, +0x31, 0x38, 0x23, 0x2a, 0x15, 0x1c, 0x07, 0x0e, 0x79, 0x70, 0x6b, 0x62, 0x5d, 0x54, 0x4f, 0x46}; + +static uchar XtimeB[256] = { +0x00, 0x0b, 0x16, 0x1d, 0x2c, 0x27, 0x3a, 0x31, 0x58, 0x53, 0x4e, 0x45, 0x74, 0x7f, 0x62, 0x69, +0xb0, 0xbb, 0xa6, 0xad, 0x9c, 0x97, 0x8a, 0x81, 0xe8, 0xe3, 0xfe, 0xf5, 0xc4, 0xcf, 0xd2, 0xd9, +0x7b, 0x70, 0x6d, 0x66, 0x57, 0x5c, 0x41, 0x4a, 0x23, 0x28, 0x35, 0x3e, 0x0f, 0x04, 0x19, 0x12, +0xcb, 0xc0, 0xdd, 0xd6, 0xe7, 0xec, 0xf1, 0xfa, 0x93, 0x98, 0x85, 0x8e, 0xbf, 0xb4, 0xa9, 0xa2, +0xf6, 0xfd, 0xe0, 0xeb, 0xda, 0xd1, 0xcc, 0xc7, 0xae, 0xa5, 0xb8, 0xb3, 0x82, 0x89, 0x94, 0x9f, +0x46, 0x4d, 0x50, 0x5b, 0x6a, 0x61, 0x7c, 0x77, 0x1e, 0x15, 0x08, 0x03, 0x32, 0x39, 0x24, 0x2f, +0x8d, 0x86, 0x9b, 0x90, 0xa1, 0xaa, 0xb7, 0xbc, 0xd5, 0xde, 0xc3, 0xc8, 0xf9, 0xf2, 0xef, 0xe4, +0x3d, 0x36, 0x2b, 0x20, 0x11, 0x1a, 0x07, 0x0c, 0x65, 0x6e, 0x73, 0x78, 0x49, 0x42, 0x5f, 0x54, +0xf7, 0xfc, 0xe1, 0xea, 0xdb, 0xd0, 0xcd, 0xc6, 0xaf, 0xa4, 0xb9, 0xb2, 0x83, 0x88, 0x95, 0x9e, +0x47, 0x4c, 0x51, 0x5a, 0x6b, 0x60, 0x7d, 0x76, 0x1f, 0x14, 0x09, 0x02, 0x33, 0x38, 0x25, 0x2e, +0x8c, 0x87, 0x9a, 0x91, 0xa0, 0xab, 0xb6, 0xbd, 0xd4, 0xdf, 0xc2, 0xc9, 0xf8, 0xf3, 0xee, 0xe5, +0x3c, 0x37, 0x2a, 0x21, 0x10, 0x1b, 0x06, 0x0d, 0x64, 0x6f, 0x72, 0x79, 0x48, 0x43, 0x5e, 0x55, +0x01, 0x0a, 0x17, 0x1c, 0x2d, 0x26, 0x3b, 0x30, 0x59, 0x52, 0x4f, 0x44, 0x75, 0x7e, 0x63, 0x68, +0xb1, 0xba, 0xa7, 0xac, 0x9d, 0x96, 0x8b, 0x80, 0xe9, 0xe2, 0xff, 0xf4, 0xc5, 0xce, 0xd3, 0xd8, +0x7a, 0x71, 0x6c, 0x67, 0x56, 0x5d, 0x40, 0x4b, 0x22, 0x29, 0x34, 0x3f, 0x0e, 0x05, 0x18, 0x13, +0xca, 0xc1, 0xdc, 0xd7, 0xe6, 0xed, 0xf0, 0xfb, 0x92, 0x99, 0x84, 0x8f, 0xbe, 0xb5, 0xa8, 0xa3}; + +static uchar XtimeD[256] = { +0x00, 0x0d, 0x1a, 0x17, 0x34, 0x39, 0x2e, 0x23, 0x68, 0x65, 0x72, 0x7f, 0x5c, 0x51, 0x46, 0x4b, +0xd0, 0xdd, 0xca, 0xc7, 0xe4, 0xe9, 0xfe, 0xf3, 0xb8, 0xb5, 0xa2, 0xaf, 0x8c, 0x81, 0x96, 0x9b, +0xbb, 0xb6, 0xa1, 0xac, 0x8f, 0x82, 0x95, 0x98, 0xd3, 0xde, 0xc9, 0xc4, 0xe7, 0xea, 0xfd, 0xf0, +0x6b, 0x66, 0x71, 0x7c, 0x5f, 0x52, 0x45, 0x48, 0x03, 0x0e, 0x19, 0x14, 0x37, 0x3a, 0x2d, 0x20, +0x6d, 0x60, 0x77, 0x7a, 0x59, 0x54, 0x43, 0x4e, 0x05, 0x08, 0x1f, 0x12, 0x31, 0x3c, 0x2b, 0x26, +0xbd, 0xb0, 0xa7, 0xaa, 0x89, 0x84, 0x93, 0x9e, 0xd5, 0xd8, 0xcf, 0xc2, 0xe1, 0xec, 0xfb, 0xf6, +0xd6, 0xdb, 0xcc, 0xc1, 0xe2, 0xef, 0xf8, 0xf5, 0xbe, 0xb3, 0xa4, 0xa9, 0x8a, 0x87, 0x90, 0x9d, +0x06, 0x0b, 0x1c, 0x11, 0x32, 0x3f, 0x28, 0x25, 0x6e, 0x63, 0x74, 0x79, 0x5a, 0x57, 0x40, 0x4d, +0xda, 0xd7, 0xc0, 0xcd, 0xee, 0xe3, 0xf4, 0xf9, 0xb2, 0xbf, 0xa8, 0xa5, 0x86, 0x8b, 0x9c, 0x91, +0x0a, 0x07, 0x10, 0x1d, 0x3e, 0x33, 0x24, 0x29, 0x62, 0x6f, 0x78, 0x75, 0x56, 0x5b, 0x4c, 0x41, +0x61, 0x6c, 0x7b, 0x76, 0x55, 0x58, 0x4f, 0x42, 0x09, 0x04, 0x13, 0x1e, 0x3d, 0x30, 0x27, 0x2a, +0xb1, 0xbc, 0xab, 0xa6, 0x85, 0x88, 0x9f, 0x92, 0xd9, 0xd4, 0xc3, 0xce, 0xed, 0xe0, 0xf7, 0xfa, +0xb7, 0xba, 0xad, 0xa0, 0x83, 0x8e, 0x99, 0x94, 0xdf, 0xd2, 0xc5, 0xc8, 0xeb, 0xe6, 0xf1, 0xfc, +0x67, 0x6a, 0x7d, 0x70, 0x53, 0x5e, 0x49, 0x44, 0x0f, 0x02, 0x15, 0x18, 0x3b, 0x36, 0x21, 0x2c, +0x0c, 0x01, 0x16, 0x1b, 0x38, 0x35, 0x22, 0x2f, 0x64, 0x69, 0x7e, 0x73, 0x50, 0x5d, 0x4a, 0x47, +0xdc, 0xd1, 0xc6, 0xcb, 0xe8, 0xe5, 0xf2, 0xff, 0xb4, 0xb9, 0xae, 0xa3, 0x80, 0x8d, 0x9a, 0x97}; + +static uchar XtimeE[256] = { +0x00, 0x0e, 0x1c, 0x12, 0x38, 0x36, 0x24, 0x2a, 0x70, 0x7e, 0x6c, 0x62, 0x48, 0x46, 0x54, 0x5a, +0xe0, 0xee, 0xfc, 0xf2, 0xd8, 0xd6, 0xc4, 0xca, 0x90, 0x9e, 0x8c, 0x82, 0xa8, 0xa6, 0xb4, 0xba, +0xdb, 0xd5, 0xc7, 0xc9, 0xe3, 0xed, 0xff, 0xf1, 0xab, 0xa5, 0xb7, 0xb9, 0x93, 0x9d, 0x8f, 0x81, +0x3b, 0x35, 0x27, 0x29, 0x03, 0x0d, 0x1f, 0x11, 0x4b, 0x45, 0x57, 0x59, 0x73, 0x7d, 0x6f, 0x61, +0xad, 0xa3, 0xb1, 0xbf, 0x95, 0x9b, 0x89, 0x87, 0xdd, 0xd3, 0xc1, 0xcf, 0xe5, 0xeb, 0xf9, 0xf7, +0x4d, 0x43, 0x51, 0x5f, 0x75, 0x7b, 0x69, 0x67, 0x3d, 0x33, 0x21, 0x2f, 0x05, 0x0b, 0x19, 0x17, +0x76, 0x78, 0x6a, 0x64, 0x4e, 0x40, 0x52, 0x5c, 0x06, 0x08, 0x1a, 0x14, 0x3e, 0x30, 0x22, 0x2c, +0x96, 0x98, 0x8a, 0x84, 0xae, 0xa0, 0xb2, 0xbc, 0xe6, 0xe8, 0xfa, 0xf4, 0xde, 0xd0, 0xc2, 0xcc, +0x41, 0x4f, 0x5d, 0x53, 0x79, 0x77, 0x65, 0x6b, 0x31, 0x3f, 0x2d, 0x23, 0x09, 0x07, 0x15, 0x1b, +0xa1, 0xaf, 0xbd, 0xb3, 0x99, 0x97, 0x85, 0x8b, 0xd1, 0xdf, 0xcd, 0xc3, 0xe9, 0xe7, 0xf5, 0xfb, +0x9a, 0x94, 0x86, 0x88, 0xa2, 0xac, 0xbe, 0xb0, 0xea, 0xe4, 0xf6, 0xf8, 0xd2, 0xdc, 0xce, 0xc0, +0x7a, 0x74, 0x66, 0x68, 0x42, 0x4c, 0x5e, 0x50, 0x0a, 0x04, 0x16, 0x18, 0x32, 0x3c, 0x2e, 0x20, +0xec, 0xe2, 0xf0, 0xfe, 0xd4, 0xda, 0xc8, 0xc6, 0x9c, 0x92, 0x80, 0x8e, 0xa4, 0xaa, 0xb8, 0xb6, +0x0c, 0x02, 0x10, 0x1e, 0x34, 0x3a, 0x28, 0x26, 0x7c, 0x72, 0x60, 0x6e, 0x44, 0x4a, 0x58, 0x56, +0x37, 0x39, 0x2b, 0x25, 0x0f, 0x01, 0x13, 0x1d, 0x47, 0x49, 0x5b, 0x55, 0x7f, 0x71, 0x63, 0x6d, +0xd7, 0xd9, 0xcb, 0xc5, 0xef, 0xe1, 0xf3, 0xfd, 0xa7, 0xa9, 0xbb, 0xb5, 0x9f, 0x91, 0x83, 0x8d}; + +// exchanges columns in each of 4 rows +// row0 - unchanged, row1- shifted left 1, +// row2 - shifted left 2 and row3 - shifted left 3 +static void ShiftRows (uchar *state) +{ +uchar tmp; + + // just substitute row 0 + state[0] = Sbox[state[0]], state[4] = Sbox[state[4]]; + state[8] = Sbox[state[8]], state[12] = Sbox[state[12]]; + + // rotate row 1 + tmp = Sbox[state[1]], state[1] = Sbox[state[5]]; + state[5] = Sbox[state[9]], state[9] = Sbox[state[13]], state[13] = tmp; + + // rotate row 2 + tmp = Sbox[state[2]], state[2] = Sbox[state[10]], state[10] = tmp; + tmp = Sbox[state[6]], state[6] = Sbox[state[14]], state[14] = tmp; + + // rotate row 3 + tmp = Sbox[state[15]], state[15] = Sbox[state[11]]; + state[11] = Sbox[state[7]], state[7] = Sbox[state[3]], state[3] = tmp; +} + +// restores columns in each of 4 rows +// row0 - unchanged, row1- shifted right 1, +// row2 - shifted right 2 and row3 - shifted right 3 +static void InvShiftRows (uchar *state) +{ +uchar tmp; + + // restore row 0 + state[0] = InvSbox[state[0]], state[4] = InvSbox[state[4]]; + state[8] = InvSbox[state[8]], state[12] = InvSbox[state[12]]; + + // restore row 1 + tmp = InvSbox[state[13]], state[13] = InvSbox[state[9]]; + state[9] = InvSbox[state[5]], state[5] = InvSbox[state[1]], state[1] = tmp; + + // restore row 2 + tmp = InvSbox[state[2]], state[2] = InvSbox[state[10]], state[10] = tmp; + tmp = InvSbox[state[6]], state[6] = InvSbox[state[14]], state[14] = tmp; + + // restore row 3 + tmp = InvSbox[state[3]], state[3] = InvSbox[state[7]]; + state[7] = InvSbox[state[11]], state[11] = InvSbox[state[15]], state[15] = tmp; +} + +// recombine and mix each row in a column +static void MixSubColumns (uchar *state) +{ +uchar tmp[4 * Nb]; + + // mixing column 0 + tmp[0] = Xtime2Sbox[state[0]] ^ Xtime3Sbox[state[5]] ^ Sbox[state[10]] ^ Sbox[state[15]]; + tmp[1] = Sbox[state[0]] ^ Xtime2Sbox[state[5]] ^ Xtime3Sbox[state[10]] ^ Sbox[state[15]]; + tmp[2] = Sbox[state[0]] ^ Sbox[state[5]] ^ Xtime2Sbox[state[10]] ^ Xtime3Sbox[state[15]]; + tmp[3] = Xtime3Sbox[state[0]] ^ Sbox[state[5]] ^ Sbox[state[10]] ^ Xtime2Sbox[state[15]]; + + // mixing column 1 + tmp[4] = Xtime2Sbox[state[4]] ^ Xtime3Sbox[state[9]] ^ Sbox[state[14]] ^ Sbox[state[3]]; + tmp[5] = Sbox[state[4]] ^ Xtime2Sbox[state[9]] ^ Xtime3Sbox[state[14]] ^ Sbox[state[3]]; + tmp[6] = Sbox[state[4]] ^ Sbox[state[9]] ^ Xtime2Sbox[state[14]] ^ Xtime3Sbox[state[3]]; + tmp[7] = Xtime3Sbox[state[4]] ^ Sbox[state[9]] ^ Sbox[state[14]] ^ Xtime2Sbox[state[3]]; + + // mixing column 2 + tmp[8] = Xtime2Sbox[state[8]] ^ Xtime3Sbox[state[13]] ^ Sbox[state[2]] ^ Sbox[state[7]]; + tmp[9] = Sbox[state[8]] ^ Xtime2Sbox[state[13]] ^ Xtime3Sbox[state[2]] ^ Sbox[state[7]]; + tmp[10] = Sbox[state[8]] ^ Sbox[state[13]] ^ Xtime2Sbox[state[2]] ^ Xtime3Sbox[state[7]]; + tmp[11] = Xtime3Sbox[state[8]] ^ Sbox[state[13]] ^ Sbox[state[2]] ^ Xtime2Sbox[state[7]]; + + // mixing column 3 + tmp[12] = Xtime2Sbox[state[12]] ^ Xtime3Sbox[state[1]] ^ Sbox[state[6]] ^ Sbox[state[11]]; + tmp[13] = Sbox[state[12]] ^ Xtime2Sbox[state[1]] ^ Xtime3Sbox[state[6]] ^ Sbox[state[11]]; + tmp[14] = Sbox[state[12]] ^ Sbox[state[1]] ^ Xtime2Sbox[state[6]] ^ Xtime3Sbox[state[11]]; + tmp[15] = Xtime3Sbox[state[12]] ^ Sbox[state[1]] ^ Sbox[state[6]] ^ Xtime2Sbox[state[11]]; + + memcpy (state, tmp, sizeof(tmp)); +} + +// restore and un-mix each row in a column +static void InvMixSubColumns (uchar *state) +{ +uchar tmp[4 * Nb]; +int i; + + // restore column 0 + tmp[0] = XtimeE[state[0]] ^ XtimeB[state[1]] ^ XtimeD[state[2]] ^ Xtime9[state[3]]; + tmp[5] = Xtime9[state[0]] ^ XtimeE[state[1]] ^ XtimeB[state[2]] ^ XtimeD[state[3]]; + tmp[10] = XtimeD[state[0]] ^ Xtime9[state[1]] ^ XtimeE[state[2]] ^ XtimeB[state[3]]; + tmp[15] = XtimeB[state[0]] ^ XtimeD[state[1]] ^ Xtime9[state[2]] ^ XtimeE[state[3]]; + + // restore column 1 + tmp[4] = XtimeE[state[4]] ^ XtimeB[state[5]] ^ XtimeD[state[6]] ^ Xtime9[state[7]]; + tmp[9] = Xtime9[state[4]] ^ XtimeE[state[5]] ^ XtimeB[state[6]] ^ XtimeD[state[7]]; + tmp[14] = XtimeD[state[4]] ^ Xtime9[state[5]] ^ XtimeE[state[6]] ^ XtimeB[state[7]]; + tmp[3] = XtimeB[state[4]] ^ XtimeD[state[5]] ^ Xtime9[state[6]] ^ XtimeE[state[7]]; + + // restore column 2 + tmp[8] = XtimeE[state[8]] ^ XtimeB[state[9]] ^ XtimeD[state[10]] ^ Xtime9[state[11]]; + tmp[13] = Xtime9[state[8]] ^ XtimeE[state[9]] ^ XtimeB[state[10]] ^ XtimeD[state[11]]; + tmp[2] = XtimeD[state[8]] ^ Xtime9[state[9]] ^ XtimeE[state[10]] ^ XtimeB[state[11]]; + tmp[7] = XtimeB[state[8]] ^ XtimeD[state[9]] ^ Xtime9[state[10]] ^ XtimeE[state[11]]; + + // restore column 3 + tmp[12] = XtimeE[state[12]] ^ XtimeB[state[13]] ^ XtimeD[state[14]] ^ Xtime9[state[15]]; + tmp[1] = Xtime9[state[12]] ^ XtimeE[state[13]] ^ XtimeB[state[14]] ^ XtimeD[state[15]]; + tmp[6] = XtimeD[state[12]] ^ Xtime9[state[13]] ^ XtimeE[state[14]] ^ XtimeB[state[15]]; + tmp[11] = XtimeB[state[12]] ^ XtimeD[state[13]] ^ Xtime9[state[14]] ^ XtimeE[state[15]]; + + for( i=0; i < 4 * Nb; i++ ) + state[i] = InvSbox[tmp[i]]; +} + +// encrypt/decrypt columns of the key +// n.b. you can replace this with +// byte-wise xor if you wish. + +static void AddRoundKey (unsigned *state, unsigned *key) +{ +int idx; + + for( idx = 0; idx < 4; idx++ ) + state[idx] ^= key[idx]; +} + +static uchar Rcon[11] = { +0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36}; + +// produce Nb bytes for each round +void ExpandKey (uchar *key, uchar *expkey) +{ +uchar tmp0, tmp1, tmp2, tmp3, tmp4; +unsigned idx; + + memcpy (expkey, key, Nk * 4); + + for( idx = Nk; idx < Nb * (Nr + 1); idx++ ) { + tmp0 = expkey[4*idx - 4]; + tmp1 = expkey[4*idx - 3]; + tmp2 = expkey[4*idx - 2]; + tmp3 = expkey[4*idx - 1]; + if( !(idx % Nk) ) { + tmp4 = tmp3; + tmp3 = Sbox[tmp0]; + tmp0 = Sbox[tmp1] ^ Rcon[idx/Nk]; + tmp1 = Sbox[tmp2]; + tmp2 = Sbox[tmp4]; + } else if( Nk > 6 && idx % Nk == 4 ) { + tmp0 = Sbox[tmp0]; + tmp1 = Sbox[tmp1]; + tmp2 = Sbox[tmp2]; + tmp3 = Sbox[tmp3]; + } + + expkey[4*idx+0] = expkey[4*idx - 4*Nk + 0] ^ tmp0; + expkey[4*idx+1] = expkey[4*idx - 4*Nk + 1] ^ tmp1; + expkey[4*idx+2] = expkey[4*idx - 4*Nk + 2] ^ tmp2; + expkey[4*idx+3] = expkey[4*idx - 4*Nk + 3] ^ tmp3; + } +} + +// encrypt one 128 bit block +void Encrypt (uchar *in, uchar *expkey, uchar *out) +{ +uchar state[Nb * 4]; +unsigned round; + + memcpy (state, in, Nb * 4); + AddRoundKey ((unsigned *)state, (unsigned *)expkey); + + for( round = 1; round < Nr + 1; round++ ) { + if( round < Nr ) + MixSubColumns (state); + else + ShiftRows (state); + + AddRoundKey ((unsigned *)state, (unsigned *)expkey + round * Nb); + } + + memcpy (out, state, sizeof(state)); +} + +void Decrypt (uchar *in, uchar *expkey, uchar *out) +{ +uchar state[Nb * 4]; +unsigned round; + + memcpy (state, in, sizeof(state)); + + AddRoundKey ((unsigned *)state, (unsigned *)expkey + Nr * Nb); + InvShiftRows(state); + + for( round = Nr; round--; ) + { + AddRoundKey ((unsigned *)state, (unsigned *)expkey + round * Nb); + if( round ) + InvMixSubColumns (state); + } + + memcpy (out, state, sizeof(state)); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/aes.h Mon Nov 09 19:27:45 2009 +0000 @@ -0,0 +1,39 @@ +// advanced encryption standard +// author: karl malbrain, malbrain@yahoo.com + +/* +This work, including the source code, documentation +and related data, is placed into the public domain. + +The orginal author is Karl Malbrain. + +THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY +OF ANY KIND, NOT EVEN THE IMPLIED WARRANTY OF +MERCHANTABILITY. THE AUTHOR OF THIS SOFTWARE, +ASSUMES _NO_ RESPONSIBILITY FOR ANY CONSEQUENCE +RESULTING FROM THE USE, MODIFICATION, OR +REDISTRIBUTION OF THIS SOFTWARE. +*/ + + +#ifndef AES_MALBRAIN +#define AES_MALBRAIN + + +// AES only supports Nb=4 +#define Nb 4 // number of columns in the state & expanded key + +#define Nk 4 // number of columns in a key +#define Nr 10 // number of rounds in encryption + + +typedef unsigned char uchar; + + +void ExpandKey (uchar *key, uchar *expkey); +void Encrypt (uchar *in, uchar *expkey, uchar *out); +void Decrypt (uchar *in, uchar *expkey, uchar *out); + + +#endif /* AES_MALBRAIN */ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/chunk.c Mon Nov 09 19:27:45 2009 +0000 @@ -0,0 +1,659 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- handle chunked data (multimedia messages) -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include <stdio.h> +#include <unistd.h> +#include <string.h> + +#include "purple.h" +#include "protocol.h" +#include "mxit.h" +#include "chunk.h" +#include "filexfer.h" + + +/*======================================================================================================================== + * Data-Type encoding + */ + +#if 0 +#include <byteswap.h> +#if (__BYTE_ORDER == __BIG_ENDIAN) +#define SWAP_64(x) (x) +#else +#define SWAP_64(x) bswap_64(x) +#endif +#endif + +/*------------------------------------------------------------------------ + * Encode a single byte in the chunked data. + * + * @param chunkdata The chunked-data buffer + * @param value The byte + * @return The number of bytes added. + */ +static int add_int8( char* chunkdata, char value ) +{ + *chunkdata = value; + + return sizeof( char ); +} + +/*------------------------------------------------------------------------ + * Encode a 16-bit value in the chunked data. + * + * @param chunkdata The chunked-data buffer + * @param value The 16-bit value + * @return The number of bytes added. + */ +static int add_int16( char* chunkdata, short value ) +{ + value = htons( value ); /* network byte-order */ + memcpy( chunkdata, &value, sizeof( short ) ); + + return sizeof( short ); +} + +/*------------------------------------------------------------------------ + * Encode a 32-bit value in the chunked data. + * + * @param chunkdata The chunked-data buffer + * @param value The 32-bit value + * @return The number of bytes added. + */ +static int add_int32( char* chunkdata, int value ) +{ + value = htonl( value ); /* network byte-order */ + memcpy( chunkdata, &value, sizeof( int ) ); + + return sizeof( int ); +} + +#if 0 +/*------------------------------------------------------------------------ + * Encode a 64-bit value in the chunked data. + * + * @param chunkdata The chunked-data buffer + * @param value The 64-bit value + * @return The number of bytes added. + */ +static int add_int64( char* chunkdata, int64_t value ) +{ + value = SWAP_64( value ); /* network byte-order */ + memcpy( chunkdata, &value, sizeof( int64_t ) ); + + return sizeof( int64_t ); +} +#endif + +/*------------------------------------------------------------------------ + * Encode a block of data in the chunked data. + * + * @param chunkdata The chunked-data buffer + * @param data The data to add + * @param datalen The length of the data to add + * @return The number of bytes added. + */ +static int add_data( char* chunkdata, const char* data, int datalen ) +{ + memcpy( chunkdata, data, datalen ); + + return datalen; +} + +/*------------------------------------------------------------------------ + * Encode a string as UTF-8 in the chunked data. + * + * @param chunkdata The chunked-data buffer + * @param str The string to encode + * @return The number of bytes in the string + */ +static int add_utf8_string( char* chunkdata, const char* str ) +{ + int pos = 0; + size_t len = strlen( str ); + + /* utf8 string length [2 bytes] */ + pos += add_int16( &chunkdata[pos], len ); + + /* utf8 string */ + pos += add_data( &chunkdata[pos], str, len ); + + return pos; +} + + +/*======================================================================================================================== + * Data-Type decoding + */ + +/*------------------------------------------------------------------------ + * Extract a single byte from the chunked data. + * + * @param chunkdata The chunked-data buffer + * @param value The byte + * @return The number of bytes extracted. + */ +static int get_int8( const char* chunkdata, char* value ) +{ + *value = *chunkdata; + + return sizeof( char ); +} + +/*------------------------------------------------------------------------ + * Extract a 16-bit value from the chunked data. + * + * @param chunkdata The chunked-data buffer + * @param value The 16-bit value + * @return The number of bytes extracted + */ +static int get_int16( const char* chunkdata, short* value ) +{ + *value = ntohs( *( (const short*) chunkdata ) ); /* host byte-order */ + + return sizeof( short ); +} + +/*------------------------------------------------------------------------ + * Extract a 32-bit value from the chunked data. + * + * @param chunkdata The chunked-data buffer + * @param value The 32-bit value + * @return The number of bytes extracted + */ +static int get_int32( const char* chunkdata, int* value ) +{ + *value = ntohl( *( (const int*) chunkdata ) ); /* host byte-order */ + + return sizeof( int ); +} + +#if 0 +/*------------------------------------------------------------------------ + * Extract a 64-bit value from the chunked data. + * + * @param chunkdata The chunked-data buffer + * @param value The 64-bit value + * @return The number of bytes extracted + */ +static int get_int64( const char* chunkdata, int64_t* value ) +{ + *value = SWAP_64( *( (const int64_t*) chunkdata ) ); /* host byte-order */ + + return sizeof( int64_t ); +} +#endif + +/*------------------------------------------------------------------------ + * Copy a block of data from the chunked data. + * + * @param chunkdata The chunked-data buffer + * @param dest Where to store the extract data + * @param datalen The length of the data to extract + * @return The number of bytes extracted + */ +static int get_data( const char* chunkdata, char* dest, int datalen ) +{ + memcpy( dest, chunkdata, datalen ); + + return datalen; +} + +/*------------------------------------------------------------------------ + * Extract a UTF-8 encoded string from the chunked data. + * + * @param chunkdata The chunked-data buffer + * @param str A pointer to extracted string. Must be g_free()'d. + * @return The number of bytes consumed + */ +static int get_utf8_string( const char* chunkdata, char* str, int maxstrlen ) +{ + int pos = 0; + short len; + int skip = 0; + + /* string length [2 bytes] */ + pos += get_int16( &chunkdata[pos], &len ); + + if ( len > maxstrlen ) { + /* possible buffer overflow */ + purple_debug_error( MXIT_PLUGIN_ID, "Buffer overflow detected (get_utf8_string)\n" ); + skip = len - maxstrlen; + len = maxstrlen; + } + + /* string data */ + pos += get_data( &chunkdata[pos], str, len ); + str[len] = '\0'; /* terminate string */ + + return pos + skip; +} + + +/*======================================================================================================================== + * Chunked Data encoding + */ + +/*------------------------------------------------------------------------ + * Encode a "reject file" chunk. (Chunk type 7) + * + * @param chunkdata Chunked-data buffer + * @param fileid A unique ID that identifies this file + * @return The number of bytes encoded in the buffer + */ +int mxit_chunk_create_reject( char* chunkdata, const char* fileid ) +{ + int pos = 0; + + /* file id [8 bytes] */ + pos += add_data( &chunkdata[pos], fileid, MXIT_CHUNK_FILEID_LEN ); + + /* rejection reason [1 byte] */ + pos += add_int8( &chunkdata[pos], REJECT_BY_USER ); + + /* rejection description [UTF-8 (optional)] */ + pos += add_utf8_string( &chunkdata[pos], "" ); + + return pos; +} + + +/*------------------------------------------------------------------------ + * Encode a "get file" request chunk. (Chunk type 8) + * + * @param chunkdata Chunked-data buffer + * @param fileid A unique ID that identifies this file + * @param filesize The number of bytes to retrieve + * @param offset The start offset in the file + * @return The number of bytes encoded in the buffer + */ +int mxit_chunk_create_get( char* chunkdata, const char* fileid, int filesize, int offset ) +{ + int pos = 0; + + /* file id [8 bytes] */ + pos += add_data( &chunkdata[pos], fileid, MXIT_CHUNK_FILEID_LEN ); + + /* offset [4 bytes] */ + pos += add_int32( &chunkdata[pos], offset ); + + /* length [4 bytes] */ + pos += add_int32( &chunkdata[pos], filesize ); + + return pos; +} + + +/*------------------------------------------------------------------------ + * Encode a "received file" chunk. (Chunk type 9) + * + * @param chunkdata Chunked-data buffer + * @param fileid A unique ID that identifies this file + * @param status The status of the file transfer (see chunk.h) + * @return The number of bytes encoded in the buffer + */ +int mxit_chunk_create_received( char* chunkdata, const char* fileid, unsigned char status ) +{ + int pos = 0; + + /* file id [8 bytes] */ + pos += add_data( &chunkdata[pos], fileid, MXIT_CHUNK_FILEID_LEN ); + + /* status [1 byte] */ + pos += add_int8( &chunkdata[pos], status ); + + return pos; +} + + +/*------------------------------------------------------------------------ + * Encode a "send file direct" chunk. (Chunk type 10) + * + * @param chunkdata Chunked-data buffer + * @param username The username of the recipient + * @param filename The name of the file being sent + * @param data The file contents + * @param datalen The size of the file contents + * @return The number of bytes encoded in the buffer + */ +int mxit_chunk_create_senddirect( char* chunkdata, const char* username, const char* filename, const unsigned char* data, int datalen ) +{ + int pos = 0; + const char* mime = NULL; + + /* data length [4 bytes] */ + pos += add_int32( &chunkdata[pos], datalen ); + + /* number of username(s) [2 bytes] */ + pos += add_int16( &chunkdata[pos], 1 ); + + /* username(s) [UTF-8] */ + pos += add_utf8_string( &chunkdata[pos], username ); + + /* filename [UTF-8] */ + pos += add_utf8_string( &chunkdata[pos], filename ); + + /* file mime type [UTF-8] */ + mime = file_mime_type( filename, (const char*) data, datalen ); + pos += add_utf8_string( &chunkdata[pos], mime ); + + /* human readable description [UTF-8 (optional)] */ + pos += add_utf8_string( &chunkdata[pos], "" ); + + /* crc [4 bytes] (0 = optional) */ + pos += add_int32( &chunkdata[pos], 0 ); + + /* the actual file data */ + pos += add_data( &chunkdata[pos], (const char *) data, datalen ); + + return pos; +} + + +/*------------------------------------------------------------------------ + * Encode a "set avatar" chunk. (Chunk type 13) + * + * @param chunkdata Chunked-data buffer + * @param data The avatar data + * @param datalen The size of the avatar data + * @return The number of bytes encoded in the buffer + */ +int mxit_chunk_create_set_avatar( char* chunkdata, const unsigned char* data, int datalen ) +{ + const char fileid[MXIT_CHUNK_FILEID_LEN]; + int pos = 0; + + /* id [8 bytes] */ + memset( &fileid, 0, sizeof( fileid ) ); /* set to 0 for file upload */ + pos += add_data( &chunkdata[pos], fileid, MXIT_CHUNK_FILEID_LEN ); + + /* size [4 bytes] */ + pos += add_int32( &chunkdata[pos], datalen ); + + /* crc [4 bytes] (0 = optional) */ + pos += add_int32( &chunkdata[pos], 0 ); + + /* the actual file data */ + pos += add_data( &chunkdata[pos], (const char *) data, datalen ); + + return pos; +} + + +/*------------------------------------------------------------------------ + * Encode a "get avatar" chunk. (Chunk type 14) + * + * @param chunkdata Chunked-data buffer + * @param mxitId The username who's avatar to download + * @param avatarId The Id of the avatar image (as string) + * @param imgsize The resolution of the avatar image + * @return The number of bytes encoded in the buffer + */ +int mxit_chunk_create_get_avatar( char* chunkdata, const char* mxitId, const char* avatarId, unsigned int imgsize ) +{ + int pos = 0; + + /* number of avatars [4 bytes] */ + pos += add_int32( &chunkdata[pos], 1 ); + + /* username [UTF-8] */ + pos += add_utf8_string( &chunkdata[pos], mxitId ); + + /* avatar id [UTF-8] */ + pos += add_utf8_string( &chunkdata[pos], avatarId ); + + /* avatar format [UTF-8] */ + pos += add_utf8_string( &chunkdata[pos], MXIT_AVATAR_TYPE ); + + /* avatar bit depth [1 byte] */ + pos += add_int8( &chunkdata[pos], MXIT_AVATAR_BITDEPT ); + + /* number of sizes [2 bytes] */ + pos += add_int16( &chunkdata[pos], 1 ); + + /* image size [4 bytes] */ + pos += add_int32( &chunkdata[pos], imgsize ); + + return pos; +} + + +/*======================================================================================================================== + * Chunked Data decoding + */ + +/*------------------------------------------------------------------------ + * Parse a received "offer file" chunk. (Chunk 6) + * + * @param chunkdata Chunked data buffer + * @param datalen The length of the chunked data + * @param offer Decoded offerfile information + */ +void mxit_chunk_parse_offer( char* chunkdata, int datalen, struct offerfile_chunk* offer ) +{ + int pos = 0; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_chunk_parse_offer (%i bytes)\n", datalen ); + + /* id [8 bytes] */ + pos += get_data( &chunkdata[pos], offer->fileid, 8); + + /* from username [UTF-8] */ + pos += get_utf8_string( &chunkdata[pos], offer->username, sizeof( offer->username ) ); + mxit_strip_domain( offer->username ); + + /* file size [4 bytes] */ + pos += get_int32( &chunkdata[pos], &(offer->filesize) ); + + /* filename [UTF-8] */ + pos += get_utf8_string( &chunkdata[pos], offer->filename, sizeof( offer->filename) ); + + /* mime type [UTF-8] */ + /* not used by libPurple */ + + /* timestamp [8 bytes] */ + /* not used by libPurple */ + + /* file description [UTF-8] */ + /* not used by libPurple */ + + /* file alternative [UTF-8] */ + /* not used by libPurple */ + + /* flags [4 bytes] */ + /* not used by libPurple */ +} + + +/*------------------------------------------------------------------------ + * Parse a received "get file" response chunk. (Chunk 8) + * + * @param chunkdata Chunked data buffer + * @param datalen The length of the chunked data + * @param offer Decoded getfile information + */ +void mxit_chunk_parse_get( char* chunkdata, int datalen, struct getfile_chunk* getfile ) +{ + int pos = 0; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_chunk_parse_file (%i bytes)\n", datalen ); + + /* id [8 bytes] */ + pos += get_data( &chunkdata[pos], getfile->fileid, 8 ); + + /* offset [4 bytes] */ + pos += get_int32( &chunkdata[pos], &(getfile->offset) ); + + /* file length [4 bytes] */ + pos += get_int32( &chunkdata[pos], &(getfile->length) ); + + /* crc [4 bytes] */ + pos += get_int32( &chunkdata[pos], &(getfile->crc) ); + + /* file data */ + getfile->data = &chunkdata[pos]; +} + + +/*------------------------------------------------------------------------ + * Parse a received splash screen chunk. (Chunk 2) + * + * @param chunkdata Chunked data buffer + * @param datalen The length of the chunked data + * @param splash Decoded splash image information + */ +static void mxit_chunk_parse_splash( char* chunkdata, int datalen, struct splash_chunk* splash ) +{ + int pos = 0; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_chunk_parse_splash (%i bytes)\n", datalen ); + + /* anchor [1 byte] */ + pos += get_int8( &chunkdata[pos], &(splash->anchor) ); + + /* time to show [1 byte] */ + pos += get_int8( &chunkdata[pos], &(splash->showtime) ); + + /* background color [4 bytes] */ + pos += get_int32( &chunkdata[pos], &(splash->bgcolor) ); + + /* file data */ + splash->data = &chunkdata[pos]; + + /* data length */ + splash->datalen = datalen - pos; +} + + +/*------------------------------------------------------------------------ + * Parse a received "custom resource" chunk. (Chunk 1) + * + * @param chunkdata Chunked data buffer + * @param datalen The length of the chunked data + * @param offer Decoded custom resource + */ +void mxit_chunk_parse_cr( char* chunkdata, int datalen, struct cr_chunk* cr ) +{ + int pos = 0; + int chunklen = 0; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_chunk_parse_cr (%i bytes)\n", datalen ); + + /* id [UTF-8] */ + pos += get_utf8_string( &chunkdata[pos], cr->id, sizeof( cr->id ) ); + + /* handle [UTF-8] */ + pos += get_utf8_string( &chunkdata[pos], cr->handle, sizeof( cr->handle ) ); + + /* operation [1 byte] */ + pos += get_int8( &chunkdata[pos], &(cr->operation) ); + + /* chunk size [4 bytes] */ + pos += get_int32( &chunkdata[pos], &chunklen ); + + /* parse the resource chunks */ + while ( chunklen > 0 ) { + struct raw_chunk* chunkhdr = ( struct raw_chunk * ) &chunkdata[pos]; + chunkhdr->length = ntohl( chunkhdr->length ); /* host byte-order */ + + /* start of chunk data */ + pos += sizeof( struct raw_chunk ); + + switch ( chunkhdr->type ) { + case CP_CHUNK_SPLASH : /* splash image */ + { + struct splash_chunk* splash = g_new0( struct splash_chunk, 1 ); + + mxit_chunk_parse_splash( &chunkdata[pos], chunkhdr->length, splash ); + + cr->resources = g_list_append( cr->resources, splash ); + break; + } + case CP_CHUNK_CLICK : /* splash click */ + { + struct splash_click_chunk* click = g_new0( struct splash_click_chunk, 1 ); + + cr->resources = g_list_append( cr->resources, click ); + break; + } + default: + purple_debug_info( MXIT_PLUGIN_ID, "Unsupported custom resource chunk received (%i)\n", chunkhdr->type ); + } + + /* skip over data to next resource chunk */ + pos += chunkhdr->length; + chunklen -= ( sizeof( struct raw_chunk ) + chunkhdr->length ); + } +} + + +/*------------------------------------------------------------------------ + * Parse a received "get avatar" response chunk. (Chunk 14) + * + * @param chunkdata Chunked data buffer + * @param datalen The length of the chunked data + * @param avatar Decoded avatar information + */ +void mxit_chunk_parse_get_avatar( char* chunkdata, int datalen, struct getavatar_chunk* avatar ) +{ + int pos = 0; + int numfiles = 0; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_chunk_parse_get_avatar (%i bytes)\n", datalen ); + + /* number of files [4 bytes] */ + pos += get_int32( &chunkdata[pos], &numfiles ); + + if ( numfiles < 1 ) /* no data */ + return; + + /* mxitId [UTF-8 string] */ + pos += get_utf8_string( &chunkdata[pos], avatar->mxitid, sizeof( avatar->mxitid ) ); + + /* avatar id [UTF-8 string] */ + pos += get_utf8_string( &chunkdata[pos], avatar->avatarid, sizeof( avatar->avatarid ) ); + + /* format [UTF-8 string] */ + pos += get_utf8_string( &chunkdata[pos], avatar->format, sizeof( avatar->format ) ); + + /* bit depth [1 byte] */ + pos += get_int8( &chunkdata[pos], &(avatar->bitdepth) ); + + /* crc [4 bytes] */ + pos += get_int32( &chunkdata[pos], &(avatar->crc) ); + + /* width [4 bytes] */ + pos += get_int32( &chunkdata[pos], &(avatar->width) ); + + /* height [4 bytes] */ + pos += get_int32( &chunkdata[pos], &(avatar->height) ); + + /* file length [4 bytes] */ + pos += get_int32( &chunkdata[pos], &(avatar->length) ); + + /* file data */ + avatar->data = &chunkdata[pos]; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/chunk.h Mon Nov 09 19:27:45 2009 +0000 @@ -0,0 +1,140 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- handle chunked data (multimedia messages) -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef _MXIT_CHUNK_H_ +#define _MXIT_CHUNK_H_ + + +#include "roster.h" + + +#define MXIT_CHUNK_FILEID_LEN 8 /* bytes */ + +/* Multimedia chunk types */ +#define CP_CHUNK_NONE 0x00 /* (0) no chunk */ +#define CP_CHUNK_CUSTOM 0x01 /* (1) custom resource */ +#define CP_CHUNK_SPLASH 0x02 /* (2) splash image */ +#define CP_CHUNK_CLICK 0x03 /* (3) splash click through */ +#define CP_CHUNK_OFFER 0x06 /* (6) offer file */ +#define CP_CHUNK_REJECT 0x07 /* (7) reject file */ +#define CP_CHUNK_GET 0x08 /* (8) get file */ +#define CP_CHUNK_RECIEVED 0x09 /* (9) received file */ +#define CP_CHUNK_DIRECT_SND 0x0A /* (10) send file direct */ +#define CP_CHUNK_DIRECT_FWD 0x0B /* (11) forward file direct */ +#define CP_CHUNK_SKIN 0x0C /* (12) MXit client skin */ +#define CP_CHUNK_SET_AVATAR 0x0D /* (13) set avatar */ +#define CP_CHUNK_GET_AVATAR 0x0E /* (14) get avatar */ +#define CP_CHUNK_END 0x7E /* (126) end */ +#define CP_CHUNK_EXT 0x7F /* (127) extended type */ + + +/* Custom Resource operations */ +#define CR_OP_UPDATE 0 +#define CR_OP_REMOVE 1 + +/* File Received status */ +#define RECV_STATUS_SUCCESS 0 +#define RECV_STATUS_PARSE_FAIL 1 +#define RECV_STATUS_CANNOT_OPEN 8 +#define RECV_STATUS_BAD_CRC 9 +#define RECV_STATUS_BAD_ID 10 + +/* File Reject status */ +#define REJECT_BY_USER 1 +#define REJECT_FILETYPE 2 +#define REJECT_NO_RESOURCES 3 +#define REJECT_BAD_RECIPIENT 4 + +/* + * a Chunk header + */ +struct raw_chunk { + guint8 type; + guint32 length; + gchar data[0]; +} __attribute__ ((packed)); + +struct offerfile_chunk { + char fileid[MXIT_CHUNK_FILEID_LEN]; + char username[MXIT_CP_MAX_JID_LEN + 1]; + int filesize; + char filename[FILENAME_MAX]; +}; + +struct getfile_chunk { + char fileid[MXIT_CHUNK_FILEID_LEN]; + int offset; + int length; + int crc; + char* data; +}; + +struct cr_chunk { + char id[64]; + char handle[64]; + char operation; + GList* resources; +}; + +struct splash_chunk { + char anchor; + char showtime; + int bgcolor; + char* data; + int datalen; +}; + +struct splash_click_chunk { + char reserved[1]; +}; + +struct getavatar_chunk { + char mxitid[50]; + char avatarid[64]; + char format[16]; + char bitdepth; + int crc; + int width; + int height; + int length; + char* data; +}; + +/* Encode chunk */ +int mxit_chunk_create_senddirect( char* chunkdata, const char* username, const char* filename, const unsigned char* data, int datalen ); +int mxit_chunk_create_reject( char* chunkdata, const char* fileid ); +int mxit_chunk_create_get( char* chunkdata, const char* fileid, int filesize, int offset ); +int mxit_chunk_create_received( char* chunkdata, const char* fileid, unsigned char status ); +int mxit_chunk_create_set_avatar( char* chunkdata, const unsigned char* data, int datalen ); +int mxit_chunk_create_get_avatar( char* chunkdata, const char* mxitId, const char* avatarId, unsigned int imgsize ); + +/* Decode chunk */ +void mxit_chunk_parse_offer( char* chunkdata, int datalen, struct offerfile_chunk* offer ); +void mxit_chunk_parse_get( char* chunkdata, int datalen, struct getfile_chunk* getfile ); +void mxit_chunk_parse_cr( char* chunkdata, int datalen, struct cr_chunk* cr ); +void mxit_chunk_parse_get_avatar( char* chunkdata, int datalen, struct getavatar_chunk* avatar ); + +#endif /* _MXIT_CHUNK_H_ */ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/cipher.c Mon Nov 09 19:27:45 2009 +0000 @@ -0,0 +1,111 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- user password encryption -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include <stdio.h> +#include <unistd.h> +#include <string.h> + +#include "purple.h" + +#include "mxit.h" +#include "cipher.h" +#include "aes.h" + + +/* password encryption */ +#define INITIAL_KEY "6170383452343567" +#define SECRET_HEADER "<mxit/>" + + +/*------------------------------------------------------------------------ + * Pad the secret data using ISO10126 Padding. + * + * @param secret The data to pad (caller must ensure buffer has enough space for padding) + * @return The total number of 128-bit blocks used + */ +static int pad_secret_data( char* secret ) +{ + int blocks = 0; + int passlen; + int padding; + + passlen = strlen( secret ); + blocks = ( passlen / 16 ) + 1; + padding = ( blocks * 16 ) - passlen; + secret[passlen] = 0x50; + secret[(blocks * 16) - 1] = padding; + + return blocks; +} + + +/*------------------------------------------------------------------------ + * Encrypt the user's cleartext password using the AES 128-bit (ECB) + * encryption algorithm. + * + * @param session The MXit session object + * @return The encrypted & encoded password. Must be g_free'd when no longer needed. + */ +char* mxit_encrypt_password( struct MXitSession* session ) +{ + char key[64]; + char exkey[512]; + char pass[64]; + char encrypted[64]; + char* base64; + int blocks; + int size; + int i; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_encrypt_password\n" ); + + memset( encrypted, 0x00, sizeof( encrypted ) ); + memset( exkey, 0x00, sizeof( exkey ) ); + memset( pass, 0x58, sizeof( pass ) ); + pass[sizeof( pass ) - 1] = '\0'; + + /* build the custom AES encryption key */ + strcpy( key, INITIAL_KEY ); + memcpy( key, session->clientkey, strlen( session->clientkey ) ); + ExpandKey( (unsigned char*) key, (unsigned char*) exkey ); + + /* build the custom data to be encrypted */ + strcpy( pass, SECRET_HEADER ); + strcat( pass, session->acc->password ); + + /* pad the secret data */ + blocks = pad_secret_data( pass ); + size = blocks * 16; + + /* now encrypt the password. we encrypt each block separately (ECB mode) */ + for ( i = 0; i < size; i += 16 ) + Encrypt( (unsigned char*) pass + i, (unsigned char*) exkey, (unsigned char*) encrypted + i ); + + /* now base64 encode the encrypted password */ + base64 = purple_base64_encode( (unsigned char*) encrypted, size ); + + return base64; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/cipher.h Mon Nov 09 19:27:45 2009 +0000 @@ -0,0 +1,36 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- user password encryption -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef _MXIT_CIPHER_H_ +#define _MXIT_CIPHER_H_ + + +struct MXitSession; + + +char* mxit_encrypt_password( struct MXitSession* session ); + + +#endif /* _MXIT_CIPHER_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/filexfer.c Mon Nov 09 19:27:45 2009 +0000 @@ -0,0 +1,454 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- file transfers (sending and receiving) -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include <stdio.h> +#include <unistd.h> +#include <string.h> + +#include "purple.h" +#include "protocol.h" +#include "mxit.h" +#include "chunk.h" +#include "filexfer.h" + + +#define MIME_TYPE_OCTETSTREAM "application/octet-stream" + + +/* supported file mime types */ +static struct mime_type { + const char* magic; + const short magic_len; + const char* mime; +} const mime_types[] = { + /* magic length mime */ + /* images */ { "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", 8, "image/png" }, /* image png */ + { "\xFF\xD8", 2, "image/jpeg" }, /* image jpeg */ + { "\x3C\x3F\x78\x6D\x6C", 5, "image/svg+xml" }, /* image SVGansi */ + { "\xEF\xBB\xBF", 3, "image/svg+xml" }, /* image SVGutf */ + { "\xEF\xBB\xBF", 3, "image/svg+xml" }, /* image SVGZ */ + /* mxit */ { "\x4d\x58\x4d", 3, "application/mxit-msgs" }, /* mxit message */ + { "\x4d\x58\x44\x01", 4, "application/mxit-mood" }, /* mxit mood */ + { "\x4d\x58\x45\x01", 4, "application/mxit-emo" }, /* mxit emoticon */ + { "\x4d\x58\x46\x01", 4, "application/mxit-emof" }, /* mxit emoticon frame */ + { "\x4d\x58\x53\x01", 4, "application/mxit-skin" }, /* mxit skin */ + /* audio */ { "\x4d\x54\x68\x64", 4, "audio/midi" }, /* audio midi */ + { "\x52\x49\x46\x46", 4, "audio/wav" }, /* audio wav */ + { "\xFF\xF1", 2, "audio/aac" }, /* audio aac1 */ + { "\xFF\xF9", 2, "audio/aac" }, /* audio aac2 */ + { "\xFF", 1, "audio/mp3" }, /* audio mp3 */ + { "\x23\x21\x41\x4D\x52\x0A", 6, "audio/amr" }, /* audio AMR */ + { "\x23\x21\x41\x4D\x52\x2D\x57\x42", 8, "audio/amr-wb" }, /* audio AMR WB */ + { "\x00\x00\x00", 3, "audio/mp4" }, /* audio mp4 */ + { "\x2E\x73\x6E\x64", 4, "audio/au" } /* audio AU */ +}; + + +/*------------------------------------------------------------------------ + * Return the MIME type matching the data file. + * + * @param filename The name of file + * @param buf The data + * @param buflen The length of the data + * @return A MIME type string + */ +const char* file_mime_type( const char* filename, const char* buf, int buflen ) +{ + unsigned int i; + + /* check for matching magic headers */ + for ( i = 0; i < ARRAY_SIZE( mime_types ); i++ ) { + + if ( buflen < mime_types[i].magic_len ) /* data is shorter than size of magic */ + continue; + + if ( memcmp( buf, mime_types[i].magic, mime_types[i].magic_len ) == 0 ) + return mime_types[i].mime; + } + + /* we did not find the MIME type, so return the default (application/octet-stream) */ + return MIME_TYPE_OCTETSTREAM; +} + + +/*------------------------------------------------------------------------ + * Cleanup and deallocate a MXit file transfer object + * + * @param xfer The file transfer object + */ +static void mxit_xfer_free( PurpleXfer* xfer ) +{ + struct mxitxfer* mx = (struct mxitxfer*) xfer->data;; + + if ( mx ) { + g_free( mx ); + xfer->data = NULL; + } +} + + +/*======================================================================================================================== + * File Transfer callbacks + */ + +/*------------------------------------------------------------------------ + * Initialise a new file transfer. + * + * @param xfer The file transfer object + */ +static void mxit_xfer_init( PurpleXfer* xfer ) +{ + struct mxitxfer* mx = (struct mxitxfer*) xfer->data; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_init\n" ); + + if ( purple_xfer_get_type( xfer ) == PURPLE_XFER_SEND ) { + /* we are trying to send a file to MXit */ + + if ( purple_xfer_get_size( xfer ) > CP_MAX_FILESIZE ) { + /* the file is too big */ + purple_xfer_error( xfer->type, xfer->account, xfer->who, _( "The file you are trying to send is too large!" ) ); + purple_xfer_cancel_local( xfer ); + return; + } + + /* start the file transfer */ + purple_xfer_start( xfer, -1, NULL, 0 ); + } + else { + /* + * we have just accepted a file transfer request from MXit. send a confirmation + * to the MXit server so that can send us the file + */ + mxit_send_file_accept( mx->session, mx->fileid, purple_xfer_get_size( xfer ), 0 ); + } +} + + +/*------------------------------------------------------------------------ + * Start the file transfer. + * + * @param xfer The file transfer object + */ +static void mxit_xfer_start( PurpleXfer* xfer ) +{ + unsigned char* buffer; + int size; + int wrote; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_start\n" ); + + if ( purple_xfer_get_type( xfer ) == PURPLE_XFER_SEND ) { + /* + * the user wants to send a file to one of his contacts. we need to create + * a buffer and copy the file data into memory and then we can send it to + * the contact. we will send the whole file with one go. + */ + buffer = g_malloc( xfer->bytes_remaining ); + size = fread( buffer, xfer->bytes_remaining, 1, xfer->dest_fp ); + + wrote = purple_xfer_write( xfer, buffer, xfer->bytes_remaining ); + if ( wrote > 0 ) + purple_xfer_set_bytes_sent( xfer, wrote ); + + /* free the buffer */ + g_free( buffer ); + buffer = NULL; + } +} + + +/*------------------------------------------------------------------------ + * The file transfer has ended. + * + * @param xfer The file transfer object + */ +static void mxit_xfer_end( PurpleXfer* xfer ) +{ + purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_end\n" ); + + /* deallocate object */ + mxit_xfer_free( xfer ); +} + + +/*------------------------------------------------------------------------ + * The file transfer (to a user) has been cancelled. + * + * @param xfer The file transfer object + */ +static void mxit_xfer_cancel_send( PurpleXfer* xfer ) +{ + purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_cancel_send\n" ); + + /* deallocate object */ + mxit_xfer_free( xfer ); +} + + +/*------------------------------------------------------------------------ + * Send the file data. + * + * @param buffer The data to sent + * @param size The length of the data to send + * @param xfer The file transfer object + * @return The amount of data actually sent + */ +static gssize mxit_xfer_write( const guchar* buffer, size_t size, PurpleXfer* xfer ) +{ + struct mxitxfer* mx = (struct mxitxfer*) xfer->data; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_write\n" ); + + if ( !mx ) { + purple_debug_warning( MXIT_PLUGIN_ID, "mxit_xfer_write: invalid internal mxit xfer data\n" ); + return -1; + } + else if ( purple_xfer_get_type( xfer ) != PURPLE_XFER_SEND ) { + purple_debug_warning( MXIT_PLUGIN_ID, "mxit_xfer_write: wrong xfer type received\n" ); + return -1; + } + + /* create and send the packet to MXit */ + mxit_send_file( mx->session, purple_xfer_get_remote_user( xfer ), purple_xfer_get_filename( xfer ), buffer, size ); + + /* the transfer is complete */ + purple_xfer_set_completed( xfer, TRUE ); + + return size; +} + + +/*------------------------------------------------------------------------ + * The user has rejected a file offer from MXit. + * + * @param xfer The file transfer object + */ +static void mxit_xfer_request_denied( PurpleXfer* xfer ) +{ + struct mxitxfer* mx = (struct mxitxfer*) xfer->data; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_request_denied\n" ); + + /* send file reject packet to MXit server */ + mxit_send_file_reject( mx->session, mx->fileid ); + + /* deallocate object */ + mxit_xfer_free( xfer ); +} + + +/*------------------------------------------------------------------------ + * The file transfer (from MXit) has been cancelled. + */ +static void mxit_xfer_cancel_recv( PurpleXfer* xfer ) +{ + purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_cancel_recv\n" ); + + /* deallocate object */ + mxit_xfer_free( xfer ); +} + + +/*======================================================================================================================== + * Callbacks from libPurple + */ + +/*------------------------------------------------------------------------ + * Indicate if file transfers are supported to this contact. + * For MXit file transfers are always supported. + * + * @param gc The connection object + * @param who The username of the contact + * @return TRUE if file transfers are supported + */ +gboolean mxit_xfer_enabled( PurpleConnection* gc, const char* who ) +{ + return TRUE; +} + + +/*------------------------------------------------------------------------ + * Create and initialize a new file transfer to a contact. + * + * @param gc The connection object + * @param who The username of the recipient + */ +PurpleXfer* mxit_xfer_new( PurpleConnection* gc, const char* who ) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + PurpleXfer* xfer = NULL; + struct mxitxfer* mx = NULL; + + /* (reference: "libpurple/ft.h") */ + xfer = purple_xfer_new( session->acc, PURPLE_XFER_SEND, who ); + + /* create file info and attach it to the file transfer */ + mx = g_new0( struct mxitxfer, 1 ); + mx->session = session; + xfer->data = mx; + + /* configure callbacks (reference: "libpurple/ft.h") */ + purple_xfer_set_init_fnc( xfer, mxit_xfer_init ); + purple_xfer_set_start_fnc( xfer, mxit_xfer_start ); + purple_xfer_set_end_fnc( xfer, mxit_xfer_end ); + purple_xfer_set_cancel_send_fnc( xfer, mxit_xfer_cancel_send ); + purple_xfer_set_write_fnc( xfer, mxit_xfer_write ); + + return xfer; +} + + +/*------------------------------------------------------------------------ + * The user has initiated a file transfer to a contact. + * + * @param gc The connection object + * @param who The username of the contact + * @param filename The filename (is NULL if request has not been accepted yet) + */ +void mxit_xfer_tx( PurpleConnection* gc, const char* who, const char* filename ) +{ + PurpleXfer *xfer = mxit_xfer_new( gc, who ); + + if ( filename ) + purple_xfer_request_accepted( xfer, filename ); + else + purple_xfer_request( xfer ); +} + + +/*======================================================================================================================== + * Calls from the MXit Protocol layer + */ + +/*------------------------------------------------------------------------ + * A file transfer offer has been received from the MXit server. + * + * @param session The MXit session object + * @param usermame The username of the sender + * @param filename The name of the file being offered + * @param filesize The size of the file being offered + * @param fileid A unique ID that identifies this file + */ +void mxit_xfer_rx_offer( struct MXitSession* session, const char* username, const char* filename, int filesize, const char* fileid ) +{ + PurpleXfer* xfer = NULL; + struct mxitxfer* mx = NULL; + + purple_debug_info( MXIT_PLUGIN_ID, "File Offer: file=%s, from=%s, size=%i\n", filename, username, filesize ); + + xfer = purple_xfer_new( session->acc, PURPLE_XFER_RECEIVE, username ); + if ( xfer ) { + /* create a new mxit xfer struct for internal use */ + mx = g_new0( struct mxitxfer, 1 ); + mx->session = session; + memcpy( mx->fileid, fileid, MXIT_CHUNK_FILEID_LEN ); + xfer->data = mx; + + purple_xfer_set_filename( xfer, filename ); + if( filesize > 0 ) + purple_xfer_set_size( xfer, filesize ); + + /* register file transfer callback functions */ + purple_xfer_set_init_fnc( xfer, mxit_xfer_init ); + purple_xfer_set_request_denied_fnc( xfer, mxit_xfer_request_denied ); + purple_xfer_set_cancel_recv_fnc( xfer, mxit_xfer_cancel_recv ); + purple_xfer_set_end_fnc( xfer, mxit_xfer_end ); + + /* give the request to the user to accept/deny */ + purple_xfer_request( xfer ); + } +} + + +/*------------------------------------------------------------------------ + * Return the libPurple file-transfer object associated with a MXit transfer + * + * @param session The MXit session object + * @param fileid A unique ID that identifies this file + */ +static PurpleXfer* find_mxit_xfer( struct MXitSession* session, const char* fileid ) +{ + GList* item = NULL; + PurpleXfer* xfer = NULL; + + item = purple_xfers_get_all(); /* list of all active transfers */ + while ( item ) { + xfer = item->data; + + if ( xfer->account == session->acc ) { + /* transfer is associated with this MXit account */ + struct mxitxfer* mx = xfer->data; + + /* does the fileid match? */ + if ( ( mx ) && ( memcmp( mx->fileid, fileid, MXIT_CHUNK_FILEID_LEN ) == 0 ) ) + break; + } + + item = g_list_next( item ); + } + + if ( item ) + return item->data; + else + return NULL; +} + +/*------------------------------------------------------------------------ + * A file has been received from the MXit server. + * + * @param session The MXit session object + * @param fileid A unique ID that identifies this file + * @param data The file data + * @param datalen The size of the data + */ +void mxit_xfer_rx_file( struct MXitSession* session, const char* fileid, const char* data, int datalen ) +{ + PurpleXfer* xfer = NULL; + struct mxitxfer* mx = NULL; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_rx_file: (size=%i)\n", datalen ); + + /* find the file-transfer object */ + xfer = find_mxit_xfer( session, fileid ); + if ( xfer ) { + mx = xfer->data; + + /* this is the transfer we have been looking for */ + purple_xfer_ref( xfer ); + purple_xfer_start( xfer, -1, NULL, 0 ); + fwrite( data, datalen, 1, xfer->dest_fp ); + purple_xfer_unref( xfer ); + purple_xfer_set_completed( xfer, TRUE ); + purple_xfer_end( xfer ); + + /* inform MXit that file was successfully received */ + mxit_send_file_received( session, fileid, RECV_STATUS_SUCCESS ); + } + else { + /* file transfer not found */ + mxit_send_file_received( session, fileid, RECV_STATUS_BAD_ID ); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/filexfer.h Mon Nov 09 19:27:45 2009 +0000 @@ -0,0 +1,50 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- file transfers (sending and receiving) -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef _MXIT_FILEXFER_H_ +#define _MXIT_FILEXFER_H_ + + +/* + * a MXit file transfer + */ +struct mxitxfer { + struct MXitSession* session; + char fileid[MXIT_CHUNK_FILEID_LEN]; +}; + +const char* file_mime_type( const char* filename, const char* buf, int buflen ); + +/* libPurple callbacks */ +gboolean mxit_xfer_enabled( PurpleConnection* gc, const char* who ); +void mxit_xfer_tx( PurpleConnection* gc, const char* who, const char* filename ); +PurpleXfer* mxit_xfer_new( PurpleConnection* gc, const char* who ); + +/* MXit Protocol callbacks */ +void mxit_xfer_rx_offer( struct MXitSession* session, const char* username, const char* filename, int filesize, const char* fileid ); +void mxit_xfer_rx_file( struct MXitSession* session, const char* fileid, const char* data, int datalen ); + + +#endif /* _MXIT_FILEXFER_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/formcmds.c Mon Nov 09 19:27:45 2009 +0000 @@ -0,0 +1,397 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- MXit Forms & Commands -- + * + * Andrew Victor <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include <string.h> +#include <glib.h> +#include <glib/gprintf.h> + +#include "purple.h" + +#include "protocol.h" +#include "mxit.h" +#include "markup.h" +#include "formcmds.h" + +#undef MXIT_DEBUG_COMMANDS + +/* + * the MXit Command identifiers + */ +typedef enum +{ + MXIT_CMD_UNKNOWN = 0, /* Unknown command */ + MXIT_CMD_CLRSCR, /* Clear screen (clrmsgscreen) */ + MXIT_CMD_SENDSMS, /* Send SMS (sendsms) */ + MXIT_CMD_REPLY, /* Reply (reply) */ + MXIT_CMD_PLATREQ, /* Platform Request (platreq) */ + MXIT_CMD_SELECTCONTACT, /* Select Contact (selc) */ + MXIT_CMD_IMAGE /* Inline image (img) */ +} MXitCommandType; + + +/* + * object for an inline image request with an URL + */ +struct ii_url_request +{ + struct RXMsgData* mx; + char* url; +}; + + +/*------------------------------------------------------------------------ + * Callback function invoked when an inline image request to a web site completes. + * + * @param url_data + * @param user_data The Markup message object + * @param url_text The data returned from the WAP site + * @param len The length of the data returned + * @param error_message Descriptive error message + */ +static void mxit_cb_ii_returned(PurpleUtilFetchUrlData* url_data, gpointer user_data, const gchar* url_text, gsize len, const gchar* error_message) +{ + struct ii_url_request* iireq = (struct ii_url_request*) user_data; + char* ii_data; + int* intptr = NULL; + int id; + +#ifdef MXIT_DEBUG_COMMANDS + purple_debug_info(MXIT_PLUGIN_ID, "Inline Image returned from %s\n", iireq->url); +#endif + + if (!url_text) { + /* no reply from the WAP site */ + purple_debug_error(MXIT_PLUGIN_ID, "Error downloading Inline Image from %s.\n", iireq->url); + goto done; + } + + /* lets first see if we dont have the inline image already in cache */ + if (g_hash_table_lookup(iireq->mx->session->iimages, iireq->url)) { + /* inline image found in the cache, so we just ignore this reply */ + goto done; + } + + /* make a copy of the data */ + ii_data = g_malloc(len); + memcpy(ii_data, (const char*) url_text, len); + + /* we now have the inline image, store it in the imagestore */ + id = purple_imgstore_add_with_id(ii_data, len, NULL); + + /* map the inline image id to purple image id */ + intptr = g_malloc(sizeof(int)); + *intptr = id; + g_hash_table_insert(iireq->mx->session->iimages, iireq->url, intptr); + + iireq->mx->flags |= PURPLE_MESSAGE_IMAGES; + +done: + iireq->mx->img_count--; + if ((iireq->mx->img_count == 0) && (iireq->mx->converted)) { + /* + * this was the last outstanding emoticon for this message, + * so we can now display it to the user. + */ + mxit_show_message(iireq->mx); + } + + g_free(iireq); +} + + +/*------------------------------------------------------------------------ + * Return the command identifier of this MXit Command. + * + * @param cmd The MXit command <key,value> map + * @return The MXit command identifier + */ +static MXitCommandType command_type(GHashTable* hash) +{ + char* op; + char* type; + + op = g_hash_table_lookup(hash, "op"); + if (op) { + if ( strcmp(op, "cmd") == 0 ) { + type = g_hash_table_lookup(hash, "type"); + if (type == NULL) /* no command provided */ + return MXIT_CMD_UNKNOWN; + else if (strcmp(type, "clrmsgscreen") == 0) /* clear the screen */ + return MXIT_CMD_CLRSCR; + else if (strcmp(type, "sendsms") == 0) /* send an SMS */ + return MXIT_CMD_SENDSMS; + else if (strcmp(type, "reply") == 0) /* list of options */ + return MXIT_CMD_REPLY; + else if (strcmp(type, "platreq") == 0) /* platform request */ + return MXIT_CMD_PLATREQ; + else if (strcmp(type, "selc") == 0) /* select contact */ + return MXIT_CMD_SELECTCONTACT; + } + else if (strcmp(op, "img") == 0) + return MXIT_CMD_IMAGE; + } + + return MXIT_CMD_UNKNOWN; +} + + +/*------------------------------------------------------------------------ + * Tokenize a MXit Command string into a <key,value> map. + * + * @param cmd The MXit command string + * @return The <key,value> hash-map, or NULL on error. + */ +static GHashTable* command_tokenize(char* cmd) +{ + GHashTable* hash = NULL; + gchar** parts; + gchar* part; + int i = 0; + +#ifdef MXIT_DEBUG_COMMANDS + purple_debug_info(MXIT_PLUGIN_ID, "command: '%s'\n", cmd); +#endif + + /* explode the command into parts */ + parts = g_strsplit(cmd, "|", 0); + + hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + + /* now break part into a key & value */ + while ((part = parts[i]) != NULL) { + char* value; + + value = strchr(parts[i], '='); /* find start of value */ + if (value != NULL) { + *value = '\0'; + value++; + } + +#ifdef MXIT_DEBUG_COMMANDS + purple_debug_info(MXIT_PLUGIN_ID, " key='%s' value='%s'\n", parts[i], value); +#endif + + g_hash_table_insert(hash, g_strdup(parts[i]), g_strdup(value)); + + i++; + } + + g_strfreev(parts); + + return hash; +} + + +/*------------------------------------------------------------------------ + * Process a ClearScreen MXit command. + * + * @param session The MXit session object + * @param from The sender of the message. + */ +static void command_clearscreen(struct MXitSession* session, const char* from) +{ + PurpleConversation *conv; + + conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, from, session->acc); + if (conv == NULL) { + purple_debug_error(MXIT_PLUGIN_ID, "Conversation with '%s' not found\n", from); + return; + } + + purple_conversation_clear_message_history(conv); // TODO: This doesn't actually clear the screen. +} + + +/*------------------------------------------------------------------------ + * Process a Reply MXit command. + * + * @param mx The received message data object + * @param hash The MXit command <key,value> map + */ +static void command_reply(struct RXMsgData* mx, GHashTable* hash) +{ + char* replymsg; + char* selmsg; + + selmsg = g_hash_table_lookup(hash, "selmsg"); /* find the selection message */ + replymsg = g_hash_table_lookup(hash, "replymsg"); /* find the reply message */ + if ((selmsg) && (replymsg)) { + gchar* seltext = g_markup_escape_text(purple_url_decode(selmsg), -1); + gchar* replytext = g_markup_escape_text(purple_url_decode(replymsg), -1); + + mxit_add_html_link( mx, replytext, seltext ); + + g_free(seltext); + g_free(replytext); + } +} + + +/*------------------------------------------------------------------------ + * Process a PlatformRequest MXit command. + * + * @param hash The MXit command <key,value> map + * @param msg The message to display (as generated so far) + */ +static void command_platformreq(GHashTable* hash, GString* msg) +{ + gchar* text = NULL; + char* selmsg; + char* dest; + + selmsg = g_hash_table_lookup(hash, "selmsg"); /* find the selection message */ + if (selmsg) { + text = g_markup_escape_text(purple_url_decode(selmsg), -1); + } + + dest = g_hash_table_lookup(hash, "dest"); /* find the destination */ + if (dest) { + g_string_append_printf(msg, "<a href=\"%s\">%s</a>", purple_url_decode(dest), (text) ? text : "Download"); /* add link to display message */ + } + + if (text) + g_free(text); +} + + +/*------------------------------------------------------------------------ + * Process an inline image MXit command. + * + * @param mx The received message data object + * @param hash The MXit command <key,value> map + * @param msg The message to display (as generated so far) + */ +static void command_image(struct RXMsgData* mx, GHashTable* hash, GString* msg) +{ + const char* img; + const char* reply; + guchar* rawimg; + char link[256]; + gsize rawimglen; + int imgid; + + img = g_hash_table_lookup(hash, "dat"); + if (img) { + rawimg = purple_base64_decode(img, &rawimglen); + //purple_util_write_data_to_file_absolute("/tmp/mxitinline.png", (char*) rawimg, rawimglen); + imgid = purple_imgstore_add_with_id(rawimg, rawimglen, NULL); + g_snprintf(link, sizeof(link), "<img id=\"%i\">", imgid); + g_string_append_printf(msg, "%s", link); + mx->flags |= PURPLE_MESSAGE_IMAGES; + } + else { + img = g_hash_table_lookup(hash, "src"); + if (img) { + struct ii_url_request* iireq; + + iireq = g_new0(struct ii_url_request,1); + iireq->url = g_strdup(purple_url_decode(img)); + iireq->mx = mx; + + g_string_append_printf(msg, "%s%s>", MXIT_II_TAG, iireq->url); + mx->got_img = TRUE; + + /* lets first see if we dont have the inline image already in cache */ + if (g_hash_table_lookup(mx->session->iimages, iireq->url)) { + /* inline image found in the cache, so we do not have to request it from the web */ + g_free(iireq); + } + else { + /* send the request for the inline image */ + purple_debug_info(MXIT_PLUGIN_ID, "sending request for inline image '%s'\n", iireq->url); + + /* request the image (reference: "libpurple/util.h") */ + purple_util_fetch_url_request(iireq->url, TRUE, NULL, TRUE, NULL, FALSE, mxit_cb_ii_returned, iireq); + mx->img_count++; + } + } + } + + /* if this is a clickable image, show a click link */ + reply = g_hash_table_lookup(hash, "replymsg"); + if (reply) { + g_string_append_printf(msg, "\n"); + mxit_add_html_link(mx, reply, "click here"); + } +} + + +/*------------------------------------------------------------------------ + * Process a received MXit Command message. + * + * @param mx The received message data object + * @param message The message text + * @return The length of the command + */ +//void mxit_command_received(struct MXitSession* session, const char* from, char* message, time_t timestamp) +int mxit_parse_command(struct RXMsgData* mx, char* message) +{ + GHashTable* hash = NULL; + char* start; + char* end; + + /* ensure that this is really a command */ + if ( ( message[0] != ':' ) || ( message[1] != ':' ) ) { + /* this is not a command */ + return 0; + } + + start = message + 2; + end = strstr(start, ":"); + if (end) { + /* end of a command found */ + *end = '\0'; /* terminate command string */ + + hash = command_tokenize(start); /* break into <key,value> pairs */ + if (hash) { + MXitCommandType type = command_type(hash); + + switch (type) { + case MXIT_CMD_CLRSCR : + command_clearscreen(mx->session, mx->from); + break; + case MXIT_CMD_REPLY : + command_reply(mx, hash); + break; + case MXIT_CMD_PLATREQ : + command_platformreq(hash, mx->msg); + break; + case MXIT_CMD_IMAGE : + command_image(mx, hash, mx->msg); + break; + default : + /* command unknown, or not currently supported */ + break; + } + g_hash_table_destroy(hash); + } + *end = ':'; + + return end - message; + } + else { + return 0; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/formcmds.h Mon Nov 09 19:27:45 2009 +0000 @@ -0,0 +1,35 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- MXit Forms & Commands -- + * + * Andrew Victor <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef _MXIT_FORMCMDS_H_ +#define _MXIT_FORMCMDS_H_ + +#include "mxit.h" + + +int mxit_parse_command(struct RXMsgData* mx, char* message); + + +#endif /* _MXIT_FORMCMDS_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/http.c Mon Nov 09 19:27:45 2009 +0000 @@ -0,0 +1,331 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- MXit client protocol implementation -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> + +#include "purple.h" + +#include "mxit.h" +#include "protocol.h" +#include "http.h" + + +/* HTTP constants */ +#define HTTP_11_200_OK "HTTP/1.1 200 OK\r\n" +#define HTTP_11_100_CONT "HTTP/1.1 100 Continue\r\n" +#define HTTP_11_SEPERATOR "\r\n\r\n" +#define HTTP_CONTENT_LEN "Content-Length: " + + +/* define to enable HTTP debugging */ +#define DEBUG_HTTP + + +/*------------------------------------------------------------------------ + * This will freeup the memory used by a HTTP request structure + * + * @param req The HTTP structure's resources should be freed up + */ +static void free_http_request( struct http_request* req ) +{ + g_free( req->host ); + g_free( req->data ); + g_free( req ); +} + + +/*------------------------------------------------------------------------ + * Write the request to the HTTP server. + * + * @param fd The file descriptor + * @param pktdata The packet data + * @param pktlen The length of the packet data + * @return Return -1 on error, otherwise 0 + */ +static int mxit_http_raw_write( int fd, const char* pktdata, int pktlen ) +{ + int written; + int res; + + written = 0; + while ( written < pktlen ) { + res = write( fd, &pktdata[written], pktlen - written ); + if ( res <= 0 ) { + /* error on socket */ + if ( errno == EAGAIN ) + continue; + + purple_debug_error( MXIT_PLUGIN_ID, "Error while writing packet to HTTP server (%i)\n", res ); + return -1; + } + written += res; + } + + return 0; +} + + +/*------------------------------------------------------------------------ + * Callback when data is received from the HTTP server. + * + * @param user_data The MXit session object + * @param source The file-descriptor on which data was received + * @param cond Condition which caused the callback (PURPLE_INPUT_READ) + */ +static void mxit_cb_http_read( gpointer user_data, gint source, PurpleInputCondition cond ) +{ + struct MXitSession* session = (struct MXitSession*) user_data; + char buf[256]; + int buflen; + char* body; + int bodylen; + char* ch; + int len; + char* tmp; + int res; + char* next; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_http_read\n" ); + + if ( session->rx_state == RX_STATE_RLEN ) { + /* we are reading in the HTTP headers */ + + /* copy partial headers if we have any part saved */ + memcpy( buf, session->rx_dbuf, session->rx_i ); + buflen = session->rx_i; + + /* read bytes from the socket */ + len = read( session->fd, buf + buflen, sizeof( buf ) - buflen ); + if ( len <= 0 ) { + /* connection has been terminated, or error occured */ + goto done; + } + +//nextpacket: + +#ifdef DEBUG_HTTP + purple_debug_info( MXIT_PLUGIN_ID, "HTTP POST READ 1: (%i)\n", len ); + dump_bytes( session, buf + buflen, len ); +#endif + + /* see if we have all the HTTP headers yet */ + ch = strstr( buf, HTTP_11_SEPERATOR ); + if ( !ch ) { + /* we need to wait for more input, so save what we have */ + session->rx_i = buflen + len; + memcpy( session->rx_dbuf, buf, session->rx_i ); + return; + } + buflen += len; + + /* we have the header's end now skip over the http seperator to get the body offset */ + ch += strlen( HTTP_11_SEPERATOR ); + *(ch - 1) = '\0'; + body = ch; + + res = buflen - ( ch - buf ); + if ( res > 0 ) { + /* we read more bytes than just the header so copy it over */ + memcpy( session->rx_dbuf, ch, res ); + session->rx_i = res; + } + else { + session->rx_i = 0; + } + + /* test for a good response */ + if ( ( strncmp( buf, HTTP_11_200_OK, strlen( HTTP_11_200_OK ) ) != 0 ) && ( strncmp( buf, HTTP_11_100_CONT, strlen( HTTP_11_100_CONT ) ) != 0 ) ) { + /* bad result */ + purple_debug_error( MXIT_PLUGIN_ID, "HTTP error: %s\n", ch ); + goto done; + } + + /* find the content-length */ + ch = (char*) purple_strcasestr( buf, HTTP_CONTENT_LEN ); + if ( !ch ) { + /* bad request. it does not contain a content-length header */ + purple_debug_error( MXIT_PLUGIN_ID, "HTTP reply received without content-length header (ignoring packet)\n" ); + goto done; + } + + /* parse the content-length */ + ch += strlen( HTTP_CONTENT_LEN ); + tmp = strchr( ch, '\r' ); + if ( !tmp ) { + purple_debug_error( MXIT_PLUGIN_ID, "Received bad HTTP reply packet (ignoring packet)\n" ); + goto done; + } + tmp = g_strndup( ch, tmp - ch ); + bodylen = atoi( tmp ); + g_free( tmp ); + tmp = NULL; + + if ( buflen > ( ( body - buf ) + bodylen ) ) { + /* we have a second packet here */ + next = body + bodylen; + session->rx_res = 0; + } + else { + session->rx_res = bodylen - session->rx_i; + } + + if ( session->rx_res == 0 ) { + /* we have read all the data */ + session->rx_i = bodylen; + session->rx_state = RX_STATE_PROC; + } + else { + /* there is still some data outstanding */ + session->rx_state = RX_STATE_DATA; + } + } + else if ( session->rx_state == RX_STATE_DATA ) { + /* we are reading the HTTP content (body) */ + + /* read bytes from the socket */ + len = read( session->fd, &session->rx_dbuf[session->rx_i], session->rx_res ); + if ( len <= 0 ) { + /* connection has been terminated, or error occured */ + goto done; + } + +#ifdef DEBUG_HTTP + purple_debug_info( MXIT_PLUGIN_ID, "HTTP POST READ 2: (%i)\n", len ); + dump_bytes( session, &session->rx_dbuf[session->rx_i], len ); +#endif + session->rx_i += len; + session->rx_res -= len; + + if ( session->rx_res == 0 ) { + /* ok, so now we have read in the whole packet */ + session->rx_state = RX_STATE_PROC; + } + } + + if ( session->rx_state == RX_STATE_PROC ) { + mxit_parse_packet( session ); + +#if 0 + if ( next ) { + /* there is another packet of which we read some data */ + + /* reset input */ + session->rx_state = RX_STATE_RLEN; + session->rx_lbuf[0] = '\0'; + session->rx_i = 0; + session->rx_res = 0; + + /* move read data */ + len = next - buf; + buflen = len; + memcpy( buf, next, len ); + goto nextpacket; + } +#endif + + /* we are done */ + goto done; + } + + return; +done: + close( session->fd ); + purple_input_remove( session->http_handler ); + session->http_handler = 0; +} + + +/*------------------------------------------------------------------------ + * Callback invoked once the connection has been established to the HTTP server, + * or on connection failure. + * + * @param user_data The MXit session object + * @param source The file-descriptor associated with the connection + * @param error_message Message explaining why the connection failed + */ +static void mxit_cb_http_connect( gpointer user_data, gint source, const gchar* error_message ) +{ + struct http_request* req = (struct http_request*) user_data; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_http_connect\n" ); + + /* source is the file descriptor of the new connection */ + if ( source < 0 ) { + purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_http_connect failed: %s\n", error_message ); + purple_connection_error( req->session->con, _( "Unable to connect to the mxit HTTP server. Please check your server server settings." ) ); + return; + } + + /* we now have an open and active TCP connection to the mxit server */ + req->session->fd = source; + + /* reset the receive buffer */ + req->session->rx_state = RX_STATE_RLEN; + req->session->rx_lbuf[0] = '\0'; + req->session->rx_i = 0; + req->session->rx_res = 0; + + /* start listening on the open connection for messages from the server (reference: "libpurple/eventloop.h") */ + req->session->http_handler = purple_input_add( req->session->fd, PURPLE_INPUT_READ, mxit_cb_http_read, req->session ); + + /* actually send the request to the HTTP server */ + mxit_http_raw_write( req->session->fd, req->data, req->datalen ); + + /* free up resources */ + free_http_request( req ); + req = NULL; +} + + +/*------------------------------------------------------------------------ + * Create HTTP connection for sending a HTTP request + * + * @param session The MXit session object + * @param host The server name to connect to + * @param port The port number to connect to + * @param data The HTTP request data (including HTTP headers etc.) + * @param datalen The HTTP request data length + */ +void mxit_http_send_request( struct MXitSession* session, char* host, int port, const char* data, int datalen ) +{ + PurpleProxyConnectData* con = NULL; + struct http_request* req; + + /* build the http request */ + req = g_new0( struct http_request, 1 ); + req->session = session; + req->host = host; + req->port = port; + req->data = g_malloc0( datalen ); + memcpy( req->data, data, datalen ); + req->datalen = datalen; + + /* open connection to the HTTP server */ + con = purple_proxy_connect( NULL, session->acc, host, port, mxit_cb_http_connect, req ); +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/http.h Mon Nov 09 19:27:45 2009 +0000 @@ -0,0 +1,47 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- MXit client protocol implementation -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + + +#ifndef _MXIT_HTTP_H_ +#define _MXIT_HTTP_H_ + + + +struct http_request +{ + struct MXitSession* session; + char* host; + int port; + char* data; + int datalen; +}; + + +void mxit_http_send_request( struct MXitSession* session, char* host, int port, const char* data, int datalen ); + + + +#endif /* _MXIT_HTTP_H_ */ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/login.c Mon Nov 09 19:27:45 2009 +0000 @@ -0,0 +1,789 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- MXit user login functionality -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include <stdio.h> +#include <string.h> + +#include "purple.h" + +#include "protocol.h" +#include "mxit.h" +#include "cipher.h" +#include "login.h" +#include "profile.h" + +/* requesting captcha size */ +#define MXIT_CAPTCHA_HEIGHT 50 +#define MXIT_CAPTCHA_WIDTH 150 + + +/* prototypes */ +static void mxit_register_view( struct MXitSession* session ); +static void get_clientinfo( struct MXitSession* session ); + + +/*------------------------------------------------------------------------ + * Create a new mxit session object + * + * @return The MXit session object + */ +static struct MXitSession* mxit_create_object( PurpleAccount* account ) +{ + struct MXitSession* session = NULL; + PurpleConnection* con = NULL; + + /* currently the wapsite does not handle a '+' in front of the username (mxitid) so we just strip it */ + if ( account->username[0] == '+' ) { + char* fixed; + + /* cut off the '+' */ + fixed = g_strdup( &account->username[1] ); + purple_account_set_username( account, fixed ); + g_free( fixed ); + } + + session = g_new0( struct MXitSession, 1 ); + + /* configure the connection (reference: "libpurple/connection.h") */ + con = purple_account_get_connection( account ); + con->proto_data = session; + con->flags |= PURPLE_CONNECTION_NO_BGCOLOR | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_HTML; + session->con = con; + + /* add account */ + session->acc = account; + + /* configure the session (reference: "libpurple/account.h") */ + g_strlcpy( session->server, purple_account_get_string( account, MXIT_CONFIG_SERVER_ADDR, DEFAULT_SERVER ), sizeof( session->server ) ); + g_strlcpy( session->http_server, purple_account_get_string( account, MXIT_CONFIG_HTTPSERVER, DEFAULT_HTTP_SERVER ), sizeof( session->http_server ) ); + session->port = purple_account_get_int( account, MXIT_CONFIG_SERVER_PORT, DEFAULT_PORT ); + g_strlcpy( session->distcode, purple_account_get_string( account, MXIT_CONFIG_DISTCODE, "" ), sizeof( session->distcode ) ); + g_strlcpy( session->clientkey, purple_account_get_string( account, MXIT_CONFIG_CLIENTKEY, "" ), sizeof( session->clientkey ) ); + g_strlcpy( session->dialcode, purple_account_get_string( account, MXIT_CONFIG_DIALCODE, "" ), sizeof( session->dialcode ) ); + session->http = purple_account_get_bool( account, MXIT_CONFIG_USE_HTTP, FALSE ); + session->iimages = g_hash_table_new( g_str_hash, g_str_equal ); + session->rx_state = RX_STATE_RLEN; + session->http_interval = MXIT_HTTP_POLL_MIN; + session->http_last_poll = time( NULL ); + + return session; +} + + +/*------------------------------------------------------------------------ + * We now have a connection established with MXit, so we can start the + * login procedure + * + * @param session The MXit session object + */ +static void mxit_connected( struct MXitSession* session ) +{ + int state; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_connected\n" ); + + session->flags |= MXIT_FLAG_CONNECTED; + purple_connection_update_progress( session->con, _( "Logging In..." ), 2, 4 ); + + /* create a timer to send a ping packet if the connection is idle */ + session->last_tx = time( NULL ); + + /* encrypt the user password */ + session->encpwd = mxit_encrypt_password( session ); + + state = purple_account_get_int( session->acc, MXIT_CONFIG_STATE, MXIT_STATE_LOGIN ); + if ( state == MXIT_STATE_LOGIN ) { + /* create and send login packet */ + mxit_send_login( session ); + } + else { + if ( !session->profile ) { + /* we have lost the session profile, so ask the user to enter it again */ + mxit_register_view( session ); + } + else { + /* create and send the register packet */ + mxit_send_register( session ); + } + } + + /* enable signals */ + mxit_enable_signals( session ); + +#ifdef MXIT_LINK_CLICK + /* register for uri click notification */ + mxit_register_uri_handler(); +#endif + + /* start the polling if this is a HTTP connection */ + if ( session->http ) { + session->http_timer_id = purple_timeout_add_seconds( 2, mxit_manage_polling, session ); + } + + /* start the tx queue manager timer */ + session->q_timer = purple_timeout_add_seconds( 2, mxit_manage_queue, session ); +} + + +/*------------------------------------------------------------------------ + * Callback invoked once the connection has been established to the MXit server, + * or on connection failure. + * + * @param user_data The MXit session object + * @param source The file-descriptor associated with the connection + * @param error_message Message explaining why the connection failed + */ +static void mxit_cb_connect( gpointer user_data, gint source, const gchar* error_message ) +{ + struct MXitSession* session = (struct MXitSession*) user_data; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_connect\n" ); + + /* source is the file descriptor of the new connection */ + if ( source < 0 ) { + purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_connect failed: %s\n", error_message ); + purple_connection_error( session->con, _( "Unable to connect to the mxit server. Please check your server server settings." ) ); + return; + } + + /* we now have an open and active TCP connection to the mxit server */ + session->fd = source; + + /* start listening on the open connection for messages from the server (reference: "libpurple/eventloop.h") */ + session->con->inpa = purple_input_add( session->fd, PURPLE_INPUT_READ, mxit_cb_rx, session ); + + mxit_connected( session ); +} + + +/*------------------------------------------------------------------------ + * Attempt to establish a connection to the MXit server. + * + * @param session The MXit session object + */ +static void mxit_login_connect( struct MXitSession* session ) +{ + PurpleProxyConnectData* data = NULL; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_login_connect\n" ); + + purple_connection_update_progress( session->con, _( "Connecting..." ), 1, 4 ); + + /* + * at this stage we have all the user's information we require + * for logging into MXit. we will now create a new connection to + * a MXit server. + */ + + if ( !session->http ) { + /* socket connection */ + data = purple_proxy_connect( session->con, session->acc, session->server, session->port, mxit_cb_connect, session ); + if ( !data ) { + purple_connection_error( session->con, _( "Unable to connect to the mxit server. Please check your server server settings." ) ); + return; + } + } + else { + /* http connection */ + mxit_connected( session ); + } +} + + +/*------------------------------------------------------------------------ + * Register a new account with MXit + * + * @param gc The connection object + * @param fields This is the fields filled-in by the user + */ +static void mxit_cb_register_ok( PurpleConnection *gc, PurpleRequestFields *fields ) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + struct MXitProfile* profile = session->profile; + const char* str; + const char* pin; + char* err = NULL; + int len; + int i; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_register_ok\n" ); + + if ( !PURPLE_CONNECTION_IS_VALID( gc ) ) { + purple_debug_error( MXIT_PLUGIN_ID, "Unable to register; account offline.\n" ); + return; + } + + /* nickname */ + str = purple_request_fields_get_string( fields, "nickname" ); + if ( ( !str ) || ( strlen( str ) < 3 ) ) { + err = "The nick name you entered is invalid."; + goto out; + } + g_strlcpy( profile->nickname, str, sizeof( profile->nickname ) ); + + /* birthdate */ + str = purple_request_fields_get_string( fields, "bday" ); + if ( ( !str ) || ( strlen( str ) < 10 ) || ( !validateDate( str ) ) ) { + err = "The birthday you entered is invalid. The correct format is: 'YYYY-MM-DD'."; + goto out; + } + g_strlcpy( profile->birthday, str, sizeof( profile->birthday ) ); + + /* gender */ + if ( purple_request_fields_get_choice( fields, "male" ) == 0 ) + profile->male = FALSE; + else + profile->male = TRUE; + + /* pin */ + pin = purple_request_fields_get_string( fields, "pin" ); + if ( !pin ) { + err = "The PIN you entered is invalid."; + goto out; + } + len = strlen( pin ); + if ( ( len < 7 ) || ( len > 10 ) ) { + err = "The PIN you entered has an invalid length [7-10]."; + goto out; + } + for ( i = 0; i < len; i++ ) { + if ( !g_ascii_isdigit( pin[i] ) ) { + err = "The PIN is invalid. It should only consist of digits [0-9]."; + goto out; + } + } + str = purple_request_fields_get_string( fields, "pin2" ); + if ( ( !str ) || ( strcmp( pin, str ) != 0 ) ) { + err = "The two PINs you entered does not match."; + goto out; + } + g_strlcpy( profile->pin, pin, sizeof( profile->pin ) ); + +out: + if ( !err ) { + purple_account_set_password( session->acc, session->profile->pin ); + mxit_login_connect( session ); + } + else { + /* show error to user */ + mxit_popup( PURPLE_NOTIFY_MSG_ERROR, _( "Registration Error" ), _( err ) ); + mxit_register_view( session ); + } +} + + +/*------------------------------------------------------------------------ + * Register a new account with MXit + * + * @param gc The connection object + * @param fields This is the fields filled-in by the user + */ +static void mxit_cb_register_cancel( PurpleConnection *gc, PurpleRequestFields *fields ) +{ + purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_register_cancel\n" ); + + /* disconnect */ + purple_account_disconnect( gc->account ); +} + + +/*------------------------------------------------------------------------ + * Show a window to the user so that he can enter his information + * + * @param session The MXit session object + */ +static void mxit_register_view( struct MXitSession* session ) +{ + struct MXitProfile* profile; + PurpleRequestFields* fields; + PurpleRequestFieldGroup* group; + PurpleRequestField* field; + + if ( !session->profile ) { + /* we need to create a profile object here */ + session->profile = g_new0( struct MXitProfile, 1 ); + } + profile = session->profile; + + fields = purple_request_fields_new(); + group = purple_request_field_group_new( NULL ); + purple_request_fields_add_group( fields, group ); + + /* mxit login name */ + field = purple_request_field_string_new( "loginname", _( "MXit Login Name" ), purple_account_get_username( session->acc ), FALSE ); + purple_request_field_string_set_editable( field, FALSE ); + purple_request_field_group_add_field( group, field ); + + /* nick name */ + field = purple_request_field_string_new( "nickname", _( "Nick Name" ), profile->nickname, FALSE ); + purple_request_field_group_add_field( group, field ); + + /* birthday */ + field = purple_request_field_string_new( "bday", _( "Birthday" ), profile->birthday, FALSE ); + purple_request_field_string_set_default_value( field, "YYYY-MM-DD" ); + purple_request_field_group_add_field( group, field ); + + /* gender */ + field = purple_request_field_choice_new( "male", _( "Gender" ), ( profile->male ) ? 1 : 0 ); + purple_request_field_choice_add( field, _( "Female" ) ); /* 0 */ + purple_request_field_choice_add( field, _( "Male" ) ); /* 1 */ + purple_request_field_group_add_field( group, field ); + + /* pin */ + field = purple_request_field_string_new( "pin", _( "PIN" ), profile->pin, FALSE ); + purple_request_field_string_set_masked( field, TRUE ); + purple_request_field_group_add_field( group, field ); + field = purple_request_field_string_new( "pin2", _( "Verify PIN" ), "", FALSE ); + purple_request_field_string_set_masked( field, TRUE ); + purple_request_field_group_add_field( group, field ); + + /* show the form to the user to complete */ + purple_request_fields( session->con, _( "Register New MXit Account" ), _( "Register New MXit Account" ), _( "Please fill in the following fields:" ), fields, _( "OK" ), G_CALLBACK( mxit_cb_register_ok ), _( "Cancel" ), G_CALLBACK( mxit_cb_register_cancel ), session->acc, NULL, NULL, session->con ); +} + + +/*------------------------------------------------------------------------ + * Callback function invoked once the Authorization information has been submitted + * to the MXit WAP site. + * + * @param url_data libPurple internal object (see purple_util_fetch_url_request) + * @param user_data The MXit session object + * @param url_text The data returned from the WAP site + * @param len The length of the data returned + * @param error_message Descriptive error message + */ +static void mxit_cb_clientinfo2( PurpleUtilFetchUrlData* url_data, gpointer user_data, const gchar* url_text, gsize len, const gchar* error_message ) +{ + struct MXitSession* session = (struct MXitSession*) user_data; + gchar** parts; + gchar** host; + int state; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_clientinfo_cb2\n" ); + +#ifdef DEBUG_PROTOCOL + purple_debug_info( MXIT_PLUGIN_ID, "HTTP RESPONSE: '%s'\n", url_text ); +#endif + + if ( !url_text ) { + /* no reply from the WAP site */ + purple_connection_error( session->con, _( "Error contacting the MXit WAP site. Please try again later." ) ); + return; + } + + /* explode the response from the WAP site into an array */ + parts = g_strsplit( url_text, ";", 15 ); + + if ( !parts ) { + /* wapserver error */ + purple_connection_error( session->con, _( "MXit is currently unable to process the request. Please try again later." ) ); + return; + } + + /* check wapsite return code */ + switch ( parts[0][0] ) { + case '0' : + /* valid reply! */ + break; + case '1' : + purple_connection_error( session->con, _( "Wrong security code entered. Please try again later." ) ); + return; + case '2' : + purple_connection_error( session->con, _( "Your session has expired. Please try again later." ) ); + return; + case '5' : + purple_connection_error( session->con, _( "Invalid country selected. Please try again." ) ); + return; + case '6' : + purple_connection_error( session->con, _( "Username is not registered. Please register first." ) ); + return; + case '7' : + purple_connection_error( session->con, _( "Username is already registered. Please choose another username." ) ); + /* this user's account already exists, so we need to change the registration login flag to be login */ + purple_account_set_int( session->acc, MXIT_CONFIG_STATE, MXIT_STATE_LOGIN ); + return; + case '3' : + case '4' : + default : + purple_connection_error( session->con, _( "Internal error. Please try again later." ) ); + return; + } + + /* now parse and split the distribution code and the client key */ + g_strlcpy( session->distcode, &parts[1][2], 36 + 1 ); + g_strlcpy( session->clientkey, &parts[1][38], 8 + 1 ); + + /* get the dial code for the client */ + g_strlcpy( session->dialcode, parts[4], sizeof( session->dialcode ) ); + + /* parse the proxy server address and port number */ + host = g_strsplit( parts[2], ":", 4 ); + g_strlcpy( session->server, &host[1][2], sizeof( session->server ) ); + session->port = atoi( &host[2][0] ); + + /* parse the http proxy server address and port number */ + g_strlcpy( session->http_server, parts[3], sizeof( session->http_server ) ); + + purple_debug_info( MXIT_PLUGIN_ID, "distcode='%s', clientkey='%s', dialcode='%s'\n", session->distcode, session->clientkey, session->dialcode ); + purple_debug_info( MXIT_PLUGIN_ID, "sock_server='%s', http_server='%s', port='%i', cc='%s'\n", session->server, session->http_server, session->port, parts[11] ); + + /* save the information (reference: "libpurple/account.h") */ + purple_account_set_string( session->acc, MXIT_CONFIG_DISTCODE, session->distcode ); + purple_account_set_string( session->acc, MXIT_CONFIG_CLIENTKEY, session->clientkey ); + purple_account_set_string( session->acc, MXIT_CONFIG_DIALCODE, session->dialcode ); + purple_account_set_string( session->acc, MXIT_CONFIG_SERVER_ADDR, session->server ); + purple_account_set_int( session->acc, MXIT_CONFIG_SERVER_PORT, session->port ); + purple_account_set_string( session->acc, MXIT_CONFIG_HTTPSERVER, session->http_server ); + + /* update the state */ + state = purple_account_get_int( session->acc, MXIT_CONFIG_STATE, MXIT_STATE_LOGIN ); + if ( state == MXIT_STATE_REGISTER1 ) + purple_account_set_int( session->acc, MXIT_CONFIG_STATE, MXIT_STATE_REGISTER2 ); + + /* freeup the memory */ + g_strfreev( host ); + g_strfreev( parts ); + + if ( state == MXIT_STATE_LOGIN ) { + /* now we can continue with the login process */ + mxit_login_connect( session ); + } + else { + /* the user is registering so we need to get more information from him/her first to complete the process */ + mxit_register_view( session ); + } +} + + +/*------------------------------------------------------------------------ + * Free up the data associated with the Authorization process. + * + * @param data The data object to free + */ +static void free_logindata( struct login_data* data ) +{ + if ( !data ) + return; + + /* free up the login resources */ + g_free( data->wapserver ); + g_free( data->sessionid ); + g_free( data->captcha ); + g_free( data->cc ); + g_free( data->locale ); + g_free( data ); +} + + +/*------------------------------------------------------------------------ + * This function is called when the user accepts the Authorization form. + * + * @param gc The connection object + * @param fields The list of fields in the accepted form + */ +static void mxit_cb_captcha_ok( PurpleConnection* gc, PurpleRequestFields* fields ) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + PurpleUtilFetchUrlData* url_data; + PurpleRequestField* field; + const char* captcha_resp; + GList* entries; + GList* entry; + char* url; + int state; + + /* get the captcha response */ + captcha_resp = purple_request_fields_get_string( fields, "code" ); + if ( ( captcha_resp == NULL ) || ( captcha_resp[0] == '\0' ) ) { + /* the user did not fill in the captcha */ + mxit_popup( PURPLE_NOTIFY_MSG_ERROR, _( "Error" ), _( "You did not enter the security code" ) ); + free_logindata( session->logindata ); + purple_account_disconnect( session->acc ); + return; + } + + /* get chosen country */ + field = purple_request_fields_get_field( fields, "country" ); + entries = purple_request_field_list_get_selected( field ); + entry = g_list_first( entries ); + session->logindata->cc = purple_request_field_list_get_data( field, entry->data ); + purple_account_set_string( session->acc, MXIT_CONFIG_COUNTRYCODE, session->logindata->cc ); + + /* get chosen language */ + field = purple_request_fields_get_field( fields, "locale" ); + entries = purple_request_field_list_get_selected( field ); + entry = g_list_first( entries ); + session->logindata->locale = purple_request_field_list_get_data( field, entry->data ); + purple_account_set_string( session->acc, MXIT_CONFIG_LOCALE, session->logindata->locale ); + +#ifdef DEBUG_PROTOCOL + purple_debug_info( MXIT_PLUGIN_ID, "cc='%s', locale='%s', captcha='%s'\n", session->logindata->cc, session->logindata->locale, captcha_resp ); +#endif + + /* get state */ + state = purple_account_get_int( session->acc, MXIT_CONFIG_STATE, MXIT_STATE_LOGIN ); + + url = g_strdup_printf( "%s?type=getpid&sessionid=%s&login=%s&ver=%s&clientid=%s&cat=%s&chalresp=%s&cc=%s&loc=%s&path=%i&brand=%s&model=%s&h=%i&w=%i&ts=%li", + session->logindata->wapserver, session->logindata->sessionid, purple_url_encode( session->acc->username ), MXIT_CP_RELEASE, MXIT_CLIENT_ID, MXIT_CP_ARCH, + captcha_resp, session->logindata->cc, session->logindata->locale, ( state == MXIT_STATE_REGISTER1 ) ? 0 : 1, MXIT_CP_PLATFORM, MXIT_CP_OS, + MXIT_CAPTCHA_HEIGHT, MXIT_CAPTCHA_WIDTH, time( NULL ) ); + url_data = purple_util_fetch_url_request( url, TRUE, MXIT_HTTP_USERAGENT, TRUE, NULL, FALSE, mxit_cb_clientinfo2, session ); + +#ifdef DEBUG_PROTOCOL + purple_debug_info( MXIT_PLUGIN_ID, "HTTP REQUEST: '%s'\n", url ); +#endif + g_free( url ); + + /* free up the login resources */ + free_logindata( session->logindata ); +} + + +/*------------------------------------------------------------------------ + * This function is called when the user cancels the Authorization form. + * + * @param gc The connection object + * @param fields The list of fields in the cancelled form + */ +static void mxit_cb_captcha_cancel( PurpleConnection* gc, PurpleRequestFields* fields ) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + + /* free up the login resources */ + free_logindata( session->logindata ); + + /* we cannot continue, so we disconnect this account */ + purple_account_disconnect( session->acc ); +} + + +/*------------------------------------------------------------------------ + * Callback function invoked once the client information has been retrieved from + * the MXit WAP site. Display page where user can select their authorization information. + * + * @param url_data libPurple internal object (see purple_util_fetch_url_request) + * @param user_data The MXit session object + * @param url_text The data returned from the WAP site + * @param len The length of the data returned + * @param error_message Descriptive error message + */ +static void mxit_cb_clientinfo1( PurpleUtilFetchUrlData* url_data, gpointer user_data, const gchar* url_text, gsize len, const gchar* error_message ) +{ + struct MXitSession* session = (struct MXitSession*) user_data; + struct login_data* logindata; + PurpleRequestFields* fields; + PurpleRequestFieldGroup* group = NULL; + PurpleRequestField* field = NULL; + gchar** parts; + gchar** countries; + gchar** locales; + int i; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_clientinfo_cb1\n" ); + +#ifdef DEBUG_PROTOCOL + purple_debug_info( MXIT_PLUGIN_ID, "RESPONSE: %s\n", url_text ); +#endif + + if ( !url_text ) { + /* no reply from the WAP site */ + purple_connection_error( session->con, _( "Error contacting the MXit WAP site. Please try again later." ) ); + return; + } + + /* explode the response from the WAP site into an array */ + parts = g_strsplit( url_text, ";", 15 ); + + if ( ( !parts ) || ( parts[0][0] != '0' ) ) { + /* server could not find the user */ + purple_connection_error( session->con, _( "MXit is currently unable to process the request. Please try again later." ) ); + return; + } + + /* save received settings */ + logindata = g_new0( struct login_data, 1 ); + logindata->wapserver = g_strdup( parts[1] ); + logindata->sessionid = g_strdup( parts[2] ); + session->logindata = logindata; + + /* now generate the popup requesting the user for action */ + + fields = purple_request_fields_new(); + group = purple_request_field_group_new( NULL ); + purple_request_fields_add_group( fields, group ); + + /* add the captcha */ + logindata->captcha = purple_base64_decode( parts[3], &logindata->captcha_size ); + field = purple_request_field_image_new( "capcha", _( "Security Code" ), (gchar*) logindata->captcha, logindata->captcha_size ); + purple_request_field_group_add_field( group, field ); + + /* ask for input */ + field = purple_request_field_string_new( "code", _( "Enter Security Code" ), NULL, FALSE ); + purple_request_field_group_add_field( group, field ); + + /* choose your country, but be careful, we already know your IP! ;-) */ + countries = g_strsplit( parts[4], ",", 500 ); + field = purple_request_field_list_new( "country", _( "Your Country" ) ); + purple_request_field_list_set_multi_select( field, FALSE ); + for ( i = 0; countries[i]; i++ ) { + gchar** country; + + country = g_strsplit( countries[i], "|", 2 ); + if ( !country ) { + /* oops, this is not good, time to bail */ + break; + } + purple_request_field_list_add( field, country[1], g_strdup( country[0] ) ); + if ( strcmp( country[1], parts[6] ) == 0 ) { + /* based on the user's ip, this is his current country code, so we default to it */ + purple_request_field_list_add_selected( field, country[1] ); + } + g_strfreev( country ); + } + purple_request_field_group_add_field( group, field ); + + /* choose your language */ + locales = g_strsplit( parts[5], ",", 200 ); + field = purple_request_field_list_new( "locale", _( "Your Language" ) ); + purple_request_field_list_set_multi_select( field, FALSE ); + for ( i = 0; locales[i]; i++ ) { + gchar** locale; + + locale = g_strsplit( locales[i], "|", 2 ); + if ( !locale ) { + /* oops, this is not good, time to bail */ + break; + } + purple_request_field_list_add( field, locale[1], g_strdup( locale[0] ) ); + g_strfreev( locale ); + } + purple_request_field_list_add_selected( field, "English" ); + purple_request_field_group_add_field( group, field ); + + /* display the form to the user and wait for his/her input */ + purple_request_fields( session->con, "MXit", _( "MXit Authorization" ), _( "MXit account validation" ), fields, + _( "Continue" ), G_CALLBACK( mxit_cb_captcha_ok ), _( "Cancel" ), G_CALLBACK( mxit_cb_captcha_cancel ), session->acc, NULL, NULL, session->con ); + + /* freeup the memory */ + g_strfreev( parts ); +} + + +/*------------------------------------------------------------------------ + * Initiate a request for the client information (distribution code, client key, etc) + * required for logging in from the MXit WAP site. + * + * @param session The MXit session object + */ +static void get_clientinfo( struct MXitSession* session ) +{ + PurpleUtilFetchUrlData* url_data; + const char* wapserver; + char* url; + + purple_debug_info( MXIT_PLUGIN_ID, "get_clientinfo\n" ); + + purple_connection_update_progress( session->con, _( "Retrieving User Information..." ), 0, 4 ); + + /* get the WAP site as was configured by the user in the advanced settings */ + wapserver = purple_account_get_string( session->acc, MXIT_CONFIG_WAPSERVER, DEFAULT_WAPSITE ); + + /* reference: "libpurple/util.h" */ + url = g_strdup_printf( "%s/res/?type=challenge&getcountries=true&getlanguage=true&getimage=true&h=%i&w=%i&ts=%li", wapserver, MXIT_CAPTCHA_HEIGHT, MXIT_CAPTCHA_WIDTH, time( NULL ) ); + url_data = purple_util_fetch_url_request( url, TRUE, MXIT_HTTP_USERAGENT, TRUE, NULL, FALSE, mxit_cb_clientinfo1, session ); + +#ifdef DEBUG_PROTOCOL + purple_debug_info( MXIT_PLUGIN_ID, "HTTP REQUEST: '%s'\n", url ); +#endif + g_free( url ); +} + + +/*------------------------------------------------------------------------ + * Log the user into MXit. + * + * @param account The account object + */ +void mxit_login( PurpleAccount* account ) +{ + struct MXitSession* session = NULL; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_login\n" ); + + /* create and save a new mxit session */ + session = mxit_create_object( account ); + + /* + * before we can login we need to have a valid distribution code and client key for authentication. + * if we don't have any info saved from a previous login, we need to get it from the MXit WAP site. + * we do cache it, so this step is only done on the very first login for each account. + */ + if ( ( session->distcode == NULL ) || ( strlen( session->distcode ) == 0 ) ) { + /* this must be the very first login, so we need to retrieve the user information */ + get_clientinfo( session ); + } + else { + /* we can continue with the login */ + mxit_login_connect( session ); + } +} + + +/*------------------------------------------------------------------------ + * Perform a reconnect to the MXit server, and maintain same session object. + * + * @param account The account object + */ +void mxit_reconnect( struct MXitSession* session ) +{ + purple_debug_info( MXIT_PLUGIN_ID, "mxit_reconnect\n" ); + + /* close existing connection */ + session->flags &= ~MXIT_FLAG_CONNECTED; + purple_proxy_connect_cancel_with_handle( session->con ); + + /* perform the re-connect */ + mxit_login_connect( session ); +} + + +/*------------------------------------------------------------------------ + * Register a new account with MXit + * + * @param acc The account object + */ +void mxit_register( PurpleAccount* account ) +{ + struct MXitSession* session = NULL; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_register\n" ); + + /* create and save a new mxit session */ + session = mxit_create_object( account ); + purple_account_set_int( account, MXIT_CONFIG_STATE, MXIT_STATE_REGISTER1 ); + + get_clientinfo( session ); +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/login.h Mon Nov 09 19:27:45 2009 +0000 @@ -0,0 +1,45 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- MXit user login functionality -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef _MXIT_LOGIN_H_ +#define _MXIT_LOGIN_H_ + + +struct login_data { + char* wapserver; /* direct WAP server for postback */ + char* sessionid; /* unique session id */ + guchar* captcha; /* actual captcha (PNG) */ + gsize captcha_size; /* captcha size */ + char* cc; /* country code */ + char* locale; /* locale (language) */ +}; + + +void mxit_login( PurpleAccount* account ); +void mxit_register( PurpleAccount* account ); +void mxit_reconnect( struct MXitSession* session ); + + +#endif /* _MXIT_LOGIN_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/markup.c Mon Nov 09 19:27:45 2009 +0000 @@ -0,0 +1,1192 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- convert between MXit and libPurple markup -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include <stdio.h> +#include <unistd.h> +#include <string.h> + +#include "purple.h" + +#include "protocol.h" +#include "mxit.h" +#include "markup.h" +#include "chunk.h" +#include "formcmds.h" +#include "roster.h" + + +/* define this to enable emoticon (markup) debugging */ +#undef MXIT_DEBUG_EMO +/* define this to enable markup conversion debugging */ +#undef MXIT_DEBUG_MARKUP + + +#define MXIT_FRAME_MAGIC "MXF\x01" /* mxit emoticon magic number */ +#define MXIT_MAX_EMO_ID 16 /* maximum emoticon ID length */ +#define COLORCODE_LEN 6 /* colour code ID length */ + + +/* HTML tag types */ +#define MXIT_TAG_COLOR 0x01 /* font color tag */ +#define MXIT_TAG_SIZE 0x02 /* font size tag */ +#define MXIT_MAX_MSG_TAGS 90 /* maximum tags per message (pigdin hack work around) */ + +/* + * a HTML tag object + */ +struct tag { + char type; + char* value; +}; + + +#define MXIT_VIBE_MSG_COLOR "#9933FF" + +/* vibes */ +static const char* vibes[] = { + /* 0 */ "Cool Vibrations", + /* 1 */ "Purple Rain", + /* 2 */ "Polite", + /* 3 */ "Rock n Roll", + /* 4 */ "Summer Slumber", + /* 5 */ "Electric Razor", + /* 6 */ "S.O.S", + /* 7 */ "Jack Hammer", + /* 8 */ "Bumble Bee", + /* 9 */ "Ripple" +}; + + + +#ifdef MXIT_DEBUG_EMO +/*------------------------------------------------------------------------ + * Dump a byte buffer as hexadecimal to the console for debugging purposes. + * + * @param buf The data to dump + * @param len The length of the data + */ +static void hex_dump( const char* buf, int len ) +{ + char msg[256]; + int pos; + int i; + + purple_debug_info( MXIT_PLUGIN_ID, "Dumping data (%i bytes)\n", len ); + + memset( msg, 0x00, sizeof( msg ) ); + pos = 0; + + for ( i = 0; i < len; i++ ) { + + if ( pos == 0 ) + pos += sprintf( &msg[pos], "%04i: ", i ); + + pos += sprintf( &msg[pos], "0x%02X ", (unsigned char) buf[i] ); + + if ( i % 16 == 15 ) { + pos += sprintf( &msg[pos], "\n" ); + purple_debug_info( MXIT_PLUGIN_ID, msg ); + pos = 0; + } + else if ( i % 16 == 7 ) + pos += sprintf( &msg[pos], " " ); + } + + if ( pos > 0 ) { + pos += sprintf( &msg[pos], "\n" ); + purple_debug_info( MXIT_PLUGIN_ID, msg ); + pos = 0; + } +} +#endif + + +/*------------------------------------------------------------------------ + * Adds a link to a message + * + * @param mx The Markup message object + * @param linkname This is the what will be returned when the link gets clicked + * @param displayname This is the name for the link which will be displayed in the UI + */ +void mxit_add_html_link( struct RXMsgData* mx, const char* linkname, const char* displayname ) +{ +#ifdef MXIT_LINK_CLICK + char retstr[256]; + gchar* retstr64; + char link[256]; + int len; + + len = g_snprintf( retstr, sizeof( retstr ), "%s|%s|%s|%s|%s", MXIT_LINK_KEY, purple_account_get_username( mx->session->acc ), + purple_account_get_protocol_id( mx->session->acc ), mx->from, linkname ); + retstr64 = purple_base64_encode( (const unsigned char*) retstr, len ); + g_snprintf( link, sizeof( link ), "%s%s", MXIT_LINK_PREFIX, retstr64 ); + g_free( retstr64 ); + + g_string_append_printf( mx->msg, "<a href=\"%s\">%s</a>", link, displayname ); +#else + g_string_append_printf( mx->msg, "<b>%s</b>", linkname ); +#endif +} + + +/*------------------------------------------------------------------------ + * Extract an ASN.1 formatted length field from the data. + * + * @param data The source data + * @param size The extracted length + * @return The number of bytes extracted + */ +static unsigned int asn_getlength( const char* data, int* size ) +{ + unsigned int len = 0; + unsigned char bytes; + unsigned char byte; + int i; + + /* first byte specifies the number of bytes in the length */ + bytes = ( data[0] & ~0x80 ); + if ( bytes > sizeof( unsigned int ) ) { + /* file too big! */ + return -1; + } + data++; + + /* parse out the actual length */ + for ( i = 0; i < bytes; i++ ) { + byte = data[i]; + len <<= 8; + len += byte; + } + + *size = len; + return bytes + 1; +} + + +/*------------------------------------------------------------------------ + * Extract an ASN.1 formatted UTF-8 string field from the data. + * + * @param data The source data + * @param type Expected type of string + * @param utf8 The extracted string. Must be deallocated by caller. + * @return The number of bytes extracted + */ +static int asn_getUtf8( const char* data, char type, char** utf8 ) +{ + int len; + + /* validate the field type [1 byte] */ + if ( data[0] != type ) { + /* this is not a utf-8 string! */ + purple_debug_error( MXIT_PLUGIN_ID, "Invalid UTF-8 encoded string in ASN data (0x%02X)\n", (unsigned char) data[0] ); + return -1; + } + + len = data[1]; /* length field [1 bytes] */ + *utf8 = g_malloc( len + 1 ); + memcpy( *utf8, &data[2], len ); /* data field */ + (*utf8)[len] = '\0'; + + return ( len + 2 ); +} + + +/*------------------------------------------------------------------------ + * Free data associated with a Markup message object. + * + * @param mx The Markup message object + */ +static void free_markupdata( struct RXMsgData* mx ) +{ + if ( mx ) { + if ( mx->msg ) + g_string_free( mx->msg, TRUE ); + if ( mx->from ) + g_free( mx->from ); + g_free( mx ); + } +} + + +/*------------------------------------------------------------------------ + * Split the message into smaller messages and send them one at a time + * to pidgin to be displayed on the UI + * + * @param mx The received message object + */ +static void mxit_show_split_message( struct RXMsgData* mx ) +{ + const char* cont = "<font color=\"#999999\">continuing...</font>\n"; + GString* msg = NULL; + char* ch = NULL; + int pos = 0; + int start = 0; + int l_nl = 0; + int l_sp = 0; + int l_gt = 0; + int stop = 0; + int tags = 0; + int segs = 0; + gboolean intag = FALSE; + + /* + * awful hack to work around the awful hack in pidgin to work around GtkIMHtml's + * inefficient rendering of messages with lots of formatting changes. + * (reference: see the function pidgin_conv_write_conv() in gtkconv.c) the issue + * is that when you have more than 100 '<' characters in the message passed to + * pidgin, none of the markup (including links) are rendered and thus just dump + * all the text as is to the conversation window. this message dump is very + * confusing and makes it totally unusable. to work around this we will count + * the amount of tags and if its more than the pidgin threshold, we will just + * break the message up into smaller parts and send them seperately to pidgin. + * to the user it will look like multiple messages, but at least he will be able + * to use and understand it. + */ + + ch = mx->msg->str; + pos = start; + while ( ch[pos] ) { + + if ( ch[pos] == '<' ) { + tags++; + intag = TRUE; + } + else if ( ch[pos] == '\n' ) { + l_nl = pos; + } + else if ( ch[pos] == '>' ) { + l_gt = pos; + intag = FALSE; + } + else if ( ch[pos] == ' ' ) { + /* ignore spaces inside tags */ + if ( !intag ) + l_sp = pos; + } + else if ( ( ch[pos] == 'w' ) && ( pos + 4 < mx->msg->len ) && ( memcmp( &ch[pos], "www.", 4 ) == 0 ) ) { + tags += 2; + } + else if ( ( ch[pos] == 'h' ) && ( pos + 8 < mx->msg->len ) && ( memcmp( &ch[pos], "http://", 7 ) == 0 ) ) { + tags += 2; + } + + if ( tags > MXIT_MAX_MSG_TAGS ) { + /* we have reached the maximum amount of tags pidgin (gtk) can handle per message. + so its time to send what we have and then start building a new message */ + + /* now find the right place to break the message */ + if ( l_nl > start ) { + /* break at last '\n' char */ + stop = l_nl; + ch[stop] = '\0'; + msg = g_string_new( &ch[start] ); + ch[stop] = '\n'; + } + else if ( l_sp > start ) { + /* break at last ' ' char */ + stop = l_sp; + ch[stop] = '\0'; + msg = g_string_new( &ch[start] ); + ch[stop] = ' '; + } + else { + /* break at the last '>' char */ + char t; + stop = l_gt + 1; + t = ch[stop]; + ch[stop] = '\0'; + msg = g_string_new( &ch[start] ); + ch[stop] = t; + stop--; + } + + /* build the string */ + if ( segs ) + g_string_prepend( msg, cont ); + + /* push message to pidgin */ + serv_got_im( mx->session->con, mx->from, msg->str, mx->flags, mx->timestamp ); + g_string_free( msg, TRUE ); + msg = NULL; + + tags = 0; + segs++; + start = stop + 1; + } + + pos++; + } + + if ( start != pos ) { + /* send the last part of the message */ + + /* build the string */ + ch[pos] = '\0'; + msg = g_string_new( &ch[start] ); + ch[pos] = '\n'; + if ( segs ) + g_string_prepend( msg, cont ); + + /* push message to pidgin */ + serv_got_im( mx->session->con, mx->from, msg->str, mx->flags, mx->timestamp ); + g_string_free( msg, TRUE ); + msg = NULL; + } +} + + +/*------------------------------------------------------------------------ + * Insert custom emoticons and inline images into the message (if there + * are any), then give the message to the UI to display to the user. + * + * @param mx The received message object + */ +void mxit_show_message( struct RXMsgData* mx ) +{ + char* pos; + int start; + unsigned int end; + int emo_ofs; + char ii[128]; + char tag[64]; + int* img_id; + + if ( mx->got_img ) { + /* search and replace all emoticon tags with proper image tags */ + + while ( ( pos = strstr( mx->msg->str, MXIT_II_TAG ) ) != NULL ) { + start = pos - mx->msg->str; /* offset at which MXIT_II_TAG starts */ + emo_ofs = start + strlen( MXIT_II_TAG ); /* offset at which EMO's ID starts */ + end = emo_ofs + 1; /* offset at which MXIT_II_TAG ends */ + + while ( ( end < mx->msg->len ) && ( mx->msg->str[end] != '>' ) ) + end++; + + if ( end == mx->msg->len ) /* end of emoticon tag not found */ + break; + + memset( ii, 0x00, sizeof( ii ) ); + memcpy( ii, &mx->msg->str[emo_ofs], end - emo_ofs ); + + /* remove inline image tag */ + g_string_erase( mx->msg, start, ( end - start ) + 1 ); + + /* find the image entry */ + img_id = (int*) g_hash_table_lookup( mx->session->iimages, ii ); + if ( !img_id ) { + /* inline image not found, so we will just skip it */ + purple_debug_error( MXIT_PLUGIN_ID, "inline image NOT found (%s)\n", ii ); + } + else { + /* insert img tag */ + g_snprintf( tag, sizeof( tag ), "<img id=\"%i\">", *img_id ); + g_string_insert( mx->msg, start, tag ); + } + } + } + +#ifdef MXIT_DEBUG_MARKUP + purple_debug_info( MXIT_PLUGIN_ID, "Markup RX (converted): '%s'\n", mx->msg->str ); +#endif + + if ( mx->processed ) { + /* this message has already been taken care of, so just ignore it here */ + } + else if ( mx->chatid < 0 ) { + /* normal chat message */ + //serv_got_im( mx->session->con, mx->from, mx->msg->str, mx->flags, mx->timestamp ); + mxit_show_split_message( mx ); + } + else { + /* this is a multimx message */ + serv_got_chat_in( mx->session->con, mx->chatid, mx->from, mx->flags, mx->msg->str, mx->timestamp); + } + + /* freeup resource */ + free_markupdata( mx ); +} + + +/*------------------------------------------------------------------------ + * Extract the custom emoticon ID from the message. + * + * @param message The input data + * @param emid The extracted emoticon ID + */ +static void parse_emoticon_str( const char* message, char* emid ) +{ + int i; + + for ( i = 0; ( message[i] != '\0' && message[i] != '}' && i < MXIT_MAX_EMO_ID ); i++ ) { + emid[i] = message[i]; + } + + if ( message[i] == '\0' ) { + /* end of message reached, ignore the tag */ + emid[0] = '\0'; + } + else if ( i == MXIT_MAX_EMO_ID ) { + /* invalid tag length, ignore the tag */ + emid[0] = '\0'; + } + else + emid[i] = '\0'; +} + + +/*------------------------------------------------------------------------ + * Callback function invoked when a custom emoticon request to the WAP site completes. + * + * @param url_data + * @param user_data The Markup message object + * @param url_text The data returned from the WAP site + * @param len The length of the data returned + * @param error_message Descriptive error message + */ +static void emoticon_returned( PurpleUtilFetchUrlData* url_data, gpointer user_data, const gchar* url_text, gsize len, const gchar* error_message ) +{ + struct RXMsgData* mx = (struct RXMsgData*) user_data; + const char* data = url_text; + unsigned int pos = 0; + char emo[16]; + int id; + char* str; + int em_size = 0; + char* em_data = NULL; + char* em_id = NULL; + int* intptr = NULL; + int res; + +#ifdef MXIT_DEBUG_EMO + purple_debug_info( MXIT_PLUGIN_ID, "emoticon_returned\n" ); +#endif + + if ( !url_text ) { + /* no reply from the WAP site */ + purple_debug_error( MXIT_PLUGIN_ID, "Error contacting the MXit WAP site. Please try again later (emoticon).\n" ); + goto done; + } + +#ifdef MXIT_DEBUG_EMO + hex_dump( data, len ); +#endif + + /* parse out the emoticon */ + pos = 0; + + /* validate the binary data received from the wapsite */ + if ( memcmp( MXIT_FRAME_MAGIC, &data[pos], strlen( MXIT_FRAME_MAGIC ) ) != 0 ) { + /* bad data, magic constant is wrong */ + purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad magic)\n" ); + goto done; + } + pos += strlen( MXIT_FRAME_MAGIC ); + + /* validate the image frame desc byte */ + if ( data[pos] != '\x6F' ) { + /* bad frame desc */ + purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad frame desc)\n" ); + goto done; + } + pos++; + + /* get the data length */ + res = asn_getlength( &data[pos], &em_size ); + if ( res <= 0 ) { + /* bad frame length */ + purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad frame length)\n" ); + goto done; + } + pos += res; +#ifdef MXIT_DEBUG_EMO + purple_debug_info( MXIT_PLUGIN_ID, "read the length '%i'\n", em_size ); +#endif + + /* utf-8 (emoticon name) */ + res = asn_getUtf8( &data[pos], 0x0C, &str ); + if ( res <= 0 ) { + /* bad utf-8 string */ + purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad name string)\n" ); + goto done; + } + pos += res; +#ifdef MXIT_DEBUG_EMO + purple_debug_info( MXIT_PLUGIN_ID, "read the string '%s'\n", str ); +#endif + g_free( str ); + str = NULL; + + /* utf-8 (emoticon shortcut) */ + res = asn_getUtf8( &data[pos], 0x81, &str ); + if ( res <= 0 ) { + /* bad utf-8 string */ + purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad shortcut string)\n" ); + goto done; + } + pos += res; +#ifdef MXIT_DEBUG_EMO + purple_debug_info( MXIT_PLUGIN_ID, "read the string '%s'\n", str ); +#endif + em_id = str; + + /* validate the image data type */ + if ( data[pos] != '\x82' ) { + /* bad frame desc */ + purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad data type)\n" ); + g_free( em_id ); + goto done; + } + pos++; + + /* get the data length */ + res = asn_getlength( &data[pos], &em_size ); + if ( res <= 0 ) { + /* bad frame length */ + purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad data length)\n" ); + g_free( em_id ); + goto done; + } + pos += res; +#ifdef MXIT_DEBUG_EMO + purple_debug_info( MXIT_PLUGIN_ID, "read the length '%i'\n", em_size ); +#endif + + if ( g_hash_table_lookup( mx->session->iimages, em_id ) ) { + /* emoticon found in the table, so ignore this one */ + goto done; + } + + /* make a copy of the data */ + em_data = g_malloc( em_size ); + memcpy( em_data, &data[pos], em_size ); + + /* strip the mxit markup tags from the emoticon id */ + if ( ( em_id[0] == '.' ) && ( em_id[1] == '{' ) ) { + parse_emoticon_str( &em_id[2], emo ); + strcpy( em_id, emo ); + } + + /* we now have the emoticon, store it in the imagestore */ + id = purple_imgstore_add_with_id( em_data, em_size, NULL ); + + /* map the mxit emoticon id to purple image id */ + intptr = g_malloc( sizeof( int ) ); + *intptr = id; + g_hash_table_insert( mx->session->iimages, em_id, intptr ); + + mx->flags |= PURPLE_MESSAGE_IMAGES; +done: + mx->img_count--; + if ( ( mx->img_count == 0 ) && ( mx->converted ) ) { + /* + * this was the last outstanding emoticon for this message, + * so we can now display it to the user. + */ + mxit_show_message( mx ); + } +} + + +/*------------------------------------------------------------------------ + * Send a request to the MXit WAP site to download the specified emoticon. + * + * @param mx The Markup message object + * @param id The ID for the emoticon + */ +static void emoticon_request( struct RXMsgData* mx, const char* id ) +{ + PurpleUtilFetchUrlData* url_data; + const char* wapserver; + char* url; + + purple_debug_info( MXIT_PLUGIN_ID, "sending request for emoticon '%s'\n", id ); + + wapserver = purple_account_get_string( mx->session->acc, MXIT_CONFIG_WAPSERVER, DEFAULT_WAPSITE ); + + /* reference: "libpurple/util.h" */ + url = g_strdup_printf( "%s/res/?type=emo&mlh=%i&sc=%s&ts=%li", wapserver, MXIT_EMOTICON_SIZE, id, time( NULL ) ); + url_data = purple_util_fetch_url_request( url, TRUE, NULL, TRUE, NULL, FALSE, emoticon_returned, mx ); + g_free( url ); +} + + +/*------------------------------------------------------------------------ + * Parse a Vibe command. + * + * @param mx The Markup message object + * @param message The message text (which contains the vibe) + * @return id The length of the message to skip + */ +static int mxit_parse_vibe( struct RXMsgData* mx, const char* message ) +{ + int vibeid; + + vibeid = message[2] - '0'; + + purple_debug_info( MXIT_PLUGIN_ID, "Vibe received (%i)\n", vibeid ); + + if ( vibeid > ( ARRAY_SIZE( vibes ) - 1 ) ) { + purple_debug_warning( MXIT_PLUGIN_ID, "Unsupported vibe received (%i)\n", vibeid ); + /* unsupported vibe */ + return 0; + } + + g_string_append_printf( mx->msg, "<font color=\"%s\"><i>%s Vibe...</i></font>", MXIT_VIBE_MSG_COLOR, vibes[vibeid] ); + return 2; +} + + +/*------------------------------------------------------------------------ + * Extract the nickname from a chatroom message and display it nicely in + * libPurple-style (HTML) markup. + * + * @param mx The received message data object + * @param message The message text + * @return The length of the message to skip + */ +static int mxit_extract_chatroom_nick( struct RXMsgData* mx, char* message, int len ) +{ + int i; + + if ( message[0] == '<' ) { + /* + * The message MIGHT contains an embedded nickname. But we can't + * be sure unless we find the end-of-nickname sequence: (>\n) + * Search for it.... + */ + gboolean found = FALSE; + gchar* nickname; + + for ( i = 1; i < len; i++ ) { + if ( ( message[i] == '\n' ) && ( message[i-1] == '>' ) ) { + found = TRUE; + message[i-1] = '\0'; /* loose the '>' */ + i++; /* and skip the new-line */ + break; + } + } + + if ( found ) { + /* + * The message definitely had an embedded nickname - generate a marked-up + * message to be displayed. + */ + nickname = g_markup_escape_text( &message[1], -1 ); + + /* add nickname within some BOLD markup to the new converted message */ + g_string_append_printf( mx->msg, "<b>%s:</b> ", nickname ); + + /* free up the resources */ + g_free( nickname ); + + return i; + } + } + + return 0; +} + + + +/*------------------------------------------------------------------------ + * Convert a message containing MXit protocol markup to libPurple-style (HTML) markup. + * + * @param mx The received message data object + * @param message The message text + * @param len The length of the message + */ +void mxit_parse_markup( struct RXMsgData* mx, char* message, int len, short msgtype, int msgflags ) +{ + char tmpstr1[128]; + char* ch; + int i = 0; + + /* tags */ + gboolean tag_bold = FALSE; + gboolean tag_under = FALSE; + gboolean tag_italic = FALSE; + +#ifdef MXIT_DEBUG_MARKUP + purple_debug_info( MXIT_PLUGIN_ID, "Markup RX (original): '%s'\n", message ); +#endif + + + /* + * supported MXit markup: + * '*' bold + * '_' underline + * '/' italics + * '$' highlight text + * '.+' inc font size + * '.-' dec font size + * '#XXXXXX' foreground color + * '.{XX}' custom emoticon + * '\' escape the following character + * '::' MXit commands + */ + + + if ( is_mxit_chatroom_contact( mx->session, mx->from ) ) { + /* chatroom message, so we need to extract and skip the sender's nickname + * which is embedded inside the message */ + i = mxit_extract_chatroom_nick( mx, message, len ); + } + + /* run through the message and check for custom emoticons and markup */ + for ( ; i < len; i++ ) { + switch ( message[i] ) { + + + /* mxit markup parsing */ + case '*' : + if ( !( msgflags & CP_MSG_MARKUP ) ) { + g_string_append_c( mx->msg, message[i] ); + break; + } + + /* bold markup */ + if ( !tag_bold ) + g_string_append( mx->msg, "<b>" ); + else + g_string_append( mx->msg, "</b>" ); + tag_bold = !tag_bold; + break; + case '_' : + if ( !( msgflags & CP_MSG_MARKUP ) ) { + g_string_append_c( mx->msg, message[i] ); + break; + } + + /* underscore markup */ + if ( !tag_under ) + g_string_append( mx->msg, "<u>" ); + else + g_string_append( mx->msg, "</u>" ); + tag_under = !tag_under; + break; + case '/' : + if ( !( msgflags & CP_MSG_MARKUP ) ) { + g_string_append_c( mx->msg, message[i] ); + break; + } + + /* italics markup */ + if ( !tag_italic ) + g_string_append( mx->msg, "<i>" ); + else + g_string_append( mx->msg, "</i>" ); + tag_italic = !tag_italic; + break; + case '$' : + if ( !( msgflags & CP_MSG_MARKUP ) ) { + g_string_append_c( mx->msg, message[i] ); + break; + } + else if ( i + 1 >= len ) { + /* message too short for complete link */ + g_string_append_c( mx->msg, '$' ); + break; + } + + /* find the end tag */ + ch = strstr( &message[i + 1], "$" ); + if ( ch ) { + /* end found */ + *ch = '\0'; + mxit_add_html_link( mx, &message[i + 1], &message[i + 1] ); + *ch = '$'; + i += ( ch - &message[i + 1] ) + 1; + } + else { + g_string_append_c( mx->msg, message[i] ); + } + /* highlight text */ + break; + case '#' : + if ( !( msgflags & CP_MSG_MARKUP ) ) { + g_string_append_c( mx->msg, message[i] ); + break; + } + else if ( i + COLORCODE_LEN >= len ) { + /* message too short for complete colour code */ + g_string_append_c( mx->msg, '#' ); + break; + } + + /* foreground (text) color */ + memcpy( tmpstr1, &message[i + 1], COLORCODE_LEN ); + tmpstr1[ COLORCODE_LEN ] = '\0'; /* terminate string */ + if ( strcmp( tmpstr1, "??????" ) == 0 ) { + /* need to reset the font */ + g_string_append( mx->msg, "</font>" ); + i += COLORCODE_LEN; + } + else if ( strspn( tmpstr1, "0123456789abcdefABCDEF") == COLORCODE_LEN ) { + /* definitely a numeric colour code */ + g_string_append_printf( mx->msg, "<font color=\"#%s\">", tmpstr1 ); + i += COLORCODE_LEN; + } + else { + /* not valid colour markup */ + g_string_append_c( mx->msg, '#' ); + } + break; + case '.' : + if ( !( msgflags & CP_MSG_EMOTICON ) ) { + g_string_append_c( mx->msg, message[i] ); + break; + } + else if ( i + 1 >= len ) { + /* message too short */ + g_string_append_c( mx->msg, '.' ); + break; + } + + switch ( message[i+1] ) { + case '+' : + /* increment text size */ + g_string_append( mx->msg, "<font size=\"+1\">" ); + i++; + break; + case '-' : + /* decrement text size */ + g_string_append( mx->msg, "<font size=\"-1\">" ); + i++; + break; + case '{' : + /* custom emoticon */ + if ( i + 2 >= len ) { + /* message too short */ + g_string_append_c( mx->msg, '.' ); + break; + } + + parse_emoticon_str( &message[i+2], tmpstr1 ); + if ( tmpstr1[0] != '\0' ) { + mx->got_img = TRUE; + + if ( g_hash_table_lookup( mx->session->iimages, tmpstr1 ) ) { + /* emoticon found in the cache, so we do not have to request it from the WAPsite */ + } + else { + /* request emoticon from the WAPsite */ + mx->img_count++; + emoticon_request( mx, tmpstr1 ); + } + + g_string_append_printf( mx->msg, MXIT_II_TAG"%s>", tmpstr1 ); + i += strlen( tmpstr1 ) + 2; + } + else + g_string_append_c( mx->msg, '.' ); + + break; + default : + g_string_append_c( mx->msg, '.' ); + break; + } + break; + case '\\' : + if ( i + 1 >= len ) { + /* message too short for an escaped character */ + g_string_append_c( mx->msg, '\\' ); + } + else { + /* ignore the next character, because its been escaped */ + g_string_append_c( mx->msg, message[i + 1] ); + i++; + } + break; + + + /* command parsing */ + case ':' : + if ( i + 1 >= len ) { + /* message too short */ + g_string_append_c( mx->msg, ':' ); + break; + } + + if ( message[i+1] == '@' ) { + /* this is a vibe! */ + int size; + + if ( i + 2 >= len ) { + /* message too short */ + g_string_append_c( mx->msg, message[i] ); + break; + } + + size = mxit_parse_vibe( mx, &message[i] ); + if ( size == 0 ) + g_string_append_c( mx->msg, message[i] ); + else + i += size; + } + else if ( msgtype != CP_MSGTYPE_COMMAND ) { + /* this is not a command message */ + g_string_append_c( mx->msg, message[i] ); + } + else if ( message[i+1] == ':' ) { + /* parse out the command */ + int size; + + size = mxit_parse_command( mx, &message[i] ); + if ( size == 0 ) + g_string_append_c( mx->msg, ':' ); + else + i += size; + } + else { + g_string_append_c( mx->msg, ':' ); + } + break; + + + /* these aren't MXit markup, but are interpreted by libPurple */ + case '<' : + g_string_append( mx->msg, "<" ); + break; + case '>' : + g_string_append( mx->msg, ">" ); + break; + case '&' : + g_string_append( mx->msg, "&" ); + break; + case '"' : + g_string_append( mx->msg, """ ); + break; + + default : + /* text */ + g_string_append_c( mx->msg, message[i] ); + break; + } + } +} + + +/*------------------------------------------------------------------------ + * Insert an inline image command. + * + * @param mx The message text as processed so far. + * @oaram id The imgstore ID of the inline image. + */ +static void inline_image_add( GString* mx, int id ) +{ + PurpleStoredImage *image; + gconstpointer img_data; + gsize img_size; + gchar* enc; + + image = purple_imgstore_find_by_id( id ); + if ( image == NULL ) + return; + + img_data = purple_imgstore_get_data( image ); + img_size = purple_imgstore_get_size( image ); + + enc = purple_base64_encode( img_data, img_size ); + + g_string_append( mx, "::op=img|dat=" ); + g_string_append( mx, enc ); + g_string_append_c( mx, ':' ); + + g_free( enc ); +} + + +/*------------------------------------------------------------------------ + * Convert libpurple (HTML) markup to MXit protocol markup (for sending to MXit). + * Any MXit markup codes in the original message also need to be escaped. + * + * @param message The message text containing libPurple (HTML) markup + * @return The message text containing MXit markup + */ +char* mxit_convert_markup_tx( const char* message, int* msgtype ) +{ + GString* mx; + struct tag* tag; + GList* entry; + GList* tagstack = NULL; + char* reply; + char color[8]; + int len = strlen ( message ); + int i; + +#ifdef MXIT_DEBUG_MARKUP + purple_debug_info( MXIT_PLUGIN_ID, "Markup TX (original): '%s'\n", message ); +#endif + + /* + * libPurple uses the following HTML markup codes: + * Bold: <b>...</b> + * Italics: <i>...</i> + * Underline: <u>...</u> + * Strikethrough: <s>...</s> (NO MXIT SUPPORT) + * Font size: <font size="">...</font> + * Font type: <font face="">...</font> (NO MXIT SUPPORT) + * Font colour: <font color=#">...</font> + * Links: <a href="">...</a> + * Newline: <br> + * Inline image: <IMG ID=""> + * The following characters are also encoded: + * & " < > + */ + + /* new message data */ + mx = g_string_sized_new( len ); + + /* run through the message and check for HTML markup */ + for ( i = 0; i < len; i++ ) { + + switch ( message[i] ) { + case '<' : + if ( purple_str_has_prefix( &message[i], "<b>" ) || purple_str_has_prefix( &message[i], "</b>" ) ) { + /* bold */ + g_string_append_c( mx, '*' ); + } + else if ( purple_str_has_prefix( &message[i], "<i>" ) || purple_str_has_prefix( &message[i], "</i>" ) ) { + /* italics */ + g_string_append_c( mx, '/' ); + } + else if ( purple_str_has_prefix( &message[i], "<u>" ) || purple_str_has_prefix( &message[i], "</u>" ) ) { + /* underline */ + g_string_append_c( mx, '_' ); + } + else if ( purple_str_has_prefix( &message[i], "<br>" ) ) { + /* newline */ + g_string_append_c( mx, '\n' ); + } + else if ( purple_str_has_prefix( &message[i], "<font size=" ) ) { + /* font size */ + tag = g_new0( struct tag, 1 ); + tag->type = MXIT_TAG_SIZE; + tagstack = g_list_prepend( tagstack, tag ); + // TODO: implement size control + } + else if ( purple_str_has_prefix( &message[i], "<font color=" ) ) { + /* font colour */ + tag = g_new0( struct tag, 1 ); + tag->type = MXIT_TAG_COLOR; + tagstack = g_list_append( tagstack, tag ); + memset( color, 0x00, sizeof( color ) ); + memcpy( color, &message[i + 13], 7 ); + g_string_append( mx, color ); + } + else if ( purple_str_has_prefix( &message[i], "</font>" ) ) { + /* end of font tag */ + entry = g_list_last( tagstack ); + if ( entry ) { + tag = entry->data; + if ( tag->type == MXIT_TAG_COLOR ) { + /* font color reset */ + g_string_append( mx, "#??????" ); + } + else if ( tag->type == MXIT_TAG_SIZE ) { + /* font size */ + // TODO: implement size control + } + tagstack = g_list_remove( tagstack, tag ); + g_free( tag ); + } + } + else if ( purple_str_has_prefix( &message[i], "<IMG ID=" ) ) { + /* inline image */ + int imgid; + + if ( sscanf( &message[i+9], "%i", &imgid ) ) { + inline_image_add( mx, imgid ); + *msgtype = CP_MSGTYPE_COMMAND; /* inline image must be sent as a MXit command */ + } + } + + /* skip to end of tag ('>') */ + for ( i++; ( i < len ) && ( message[i] != '>' ) ; i++ ); + + break; + + case '*' : /* MXit bold */ + case '_' : /* MXit underline */ + case '/' : /* MXit italic */ + case '#' : /* MXit font color */ + case '$' : /* MXit highlight text */ + case '\\' : /* MXit escape backslash */ + g_string_append( mx, "\\" ); /* escape character */ + g_string_append_c( mx, message[i] ); /* character to escape */ + break; + + default: + g_string_append_c( mx, message[i] ); + break; + } + } + + /* unescape HTML entities to their literal characters (reference: "libpurple/utils.h") */ + reply = purple_unescape_html( mx->str ); + + g_string_free( mx, TRUE ); + +#ifdef MXIT_DEBUG_MARKUP + purple_debug_info( MXIT_PLUGIN_ID, "Markup TX (converted): '%s'\n", reply ); +#endif + + return reply; +} + + +/*------------------------------------------------------------------------ + * Free an emoticon entry. + * + * @param key MXit emoticon ID + * @param value Imagestore ID for emoticon + * @param user_data NULL (unused) + * @return TRUE + */ +static gboolean emoticon_entry_free( gpointer key, gpointer value, gpointer user_data ) +{ + int* imgid = value; + + /* key is a string */ + g_free( key ); + + /* value is 'id' in imagestore */ + purple_imgstore_unref_by_id( *imgid ); + g_free( value ); + + return TRUE; +} + + +/*------------------------------------------------------------------------ + * Free all entries in the emoticon cache. + * + * @param session The MXit session object + */ +void mxit_free_emoticon_cache( struct MXitSession* session ) +{ + g_hash_table_foreach_remove( session->iimages, emoticon_entry_free, NULL ); + g_hash_table_destroy ( session->iimages ); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/markup.h Mon Nov 09 19:27:45 2009 +0000 @@ -0,0 +1,40 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- convert between MXit and libPurple markup -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef _MXIT_MARKUP_H_ +#define _MXIT_MARKUP_H_ + +#define MXIT_II_TAG "<MXII=" /* inline image placeholder string */ + + +void mxit_parse_markup( struct RXMsgData* mx, char* message, int len, short msgtype, int msgflags ); +char* mxit_convert_markup_tx( const char* message, int* msgtype ); +void mxit_add_html_link( struct RXMsgData* mx, const char* linkname, const char* displayname ); +void mxit_show_message( struct RXMsgData* mx ); + +void mxit_free_emoticon_cache( struct MXitSession* session ); + + +#endif /* _MXIT_MARKUP_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/multimx.c Mon Nov 09 19:27:45 2009 +0000 @@ -0,0 +1,597 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- MultiMx GroupChat -- + * + * Andrew Victor <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include <string.h> +#include <errno.h> + +#include "purple.h" +#include "prpl.h" + +#include "protocol.h" +#include "mxit.h" +#include "multimx.h" +#include "markup.h" + + +#if 0 +static void multimx_dump(struct multimx* multimx) +{ + purple_debug_info(MXIT_PLUGIN_ID, "MultiMX:\n"); + purple_debug_info(MXIT_PLUGIN_ID, " Chat ID: %i\n", multimx->chatid); + purple_debug_info(MXIT_PLUGIN_ID, " Username: %s\n", multimx->roomid); + purple_debug_info(MXIT_PLUGIN_ID, " Alias: %s\n", multimx->roomname); + purple_debug_info(MXIT_PLUGIN_ID, " State: %i\n", multimx->state); +} +#endif + + +/*------------------------------------------------------------------------ + * Find a MultiMx session based on libpurple chatID. + * + * @param session The MXit session object + * @param id The libpurple group-chat ID + * @return The MultiMX room object (or NULL if not found) + */ +static struct multimx* find_room_by_id(struct MXitSession* session, int id) +{ + GList* x = session->rooms; + + while (x != NULL) { + struct multimx* multimx = (struct multimx *) x->data; + + if (multimx->chatid == id) + return multimx; + + x = g_list_next(x); + } + + return NULL; +} + + +/*------------------------------------------------------------------------ + * Find a MultiMx session based on Alias + * + * @param session The MXit session object + * @param roomname The UI room-name + * @return The MultiMX room object (or NULL if not found) + */ +static struct multimx* find_room_by_alias(struct MXitSession* session, const char* roomname) +{ + GList* x = session->rooms; + + while (x != NULL) { + struct multimx* multimx = (struct multimx *) x->data; + + if (!strcmp(multimx->roomname, roomname)) + return multimx; + + x = g_list_next(x); + } + + return NULL; +} + + +/*------------------------------------------------------------------------ + * Find a MultiMx session based on Username (MXit RoomId) + * + * @param session The MXit session object + * @param username The MXit RoomID (MultiMX contact username) + * @return The MultiMX room object (or NULL if not found) + */ +static struct multimx* find_room_by_username(struct MXitSession* session, const char* username) +{ + GList* x = session->rooms; + + while (x != NULL) { + struct multimx* multimx = (struct multimx *) x->data; + + if (!strcmp(multimx->roomid, username)) + return multimx; + + x = g_list_next(x); + } + + return NULL; +} + + +/*------------------------------------------------------------------------ + * Create a GroupChat room, and add to list of rooms. + * + * @param session The MXit session object + * @param roomid The MXit RoomID (MultiMX contact username) + * @param roomname The UI room-name + * @param state The initial state of the room (see multimx.h) + * @return The MultiMX room object + */ +static struct multimx* room_create(struct MXitSession* session, const char* roomid, const char* roomname, short state) +{ + struct multimx* multimx = NULL; + static int groupchatID = 1; + + purple_debug_info(MXIT_PLUGIN_ID, "Groupchat create - roomid='%s' roomname='%s'\n", roomid, roomname); + + /* Create a new GroupChat */ + multimx = g_new0(struct multimx, 1); + + /* Initialize groupchat */ + g_strlcpy(multimx->roomid, roomid, sizeof(multimx->roomid)); + g_strlcpy(multimx->roomname, roomname, sizeof(multimx->roomname)); + multimx->chatid = groupchatID++; + multimx->state = state; + + /* Add to GroupChat list */ + session->rooms = g_list_append(session->rooms, multimx); + + return multimx; +} + + +/*------------------------------------------------------------------------ + * Free the Groupchat room. + * + * @param session The MXit session object + * @param multimx The MultiMX room object to deallocate + */ +static void room_remove(struct MXitSession* session, struct multimx* multimx) +{ + /* Remove from GroupChat list */ + session->rooms = g_list_remove(session->rooms, multimx); + + /* Deallocate it */ + free (multimx); + multimx = NULL; +} + + +/*------------------------------------------------------------------------ + * Another user has join the GroupChat, add them to the member-list. + * + * @param session The MXit session object + * @param multimx The MultiMX room object + * @param nickname The nickname of the user who joined the room + */ +static void member_added(struct MXitSession* session, struct multimx* multimx, const char* nickname) +{ + PurpleConversation *convo; + + purple_debug_info(MXIT_PLUGIN_ID, "member_added: '%s'\n", nickname); + + convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, multimx->roomname, session->acc); + if (convo == NULL) { + purple_debug_error(MXIT_PLUGIN_ID, "Conversation '%s' not found\n", multimx->roomname); + return; + } + + purple_conv_chat_add_user(PURPLE_CONV_CHAT(convo), nickname, NULL, PURPLE_CBFLAGS_NONE, TRUE); +} + + +/*------------------------------------------------------------------------ + * Another user has left the GroupChat, remove them from the member-list. + * + * @param session The MXit session object + * @param multimx The MultiMX room object + * @param nickname The nickname of the user who left the room + */ +static void member_removed(struct MXitSession* session, struct multimx* multimx, const char* nickname) +{ + PurpleConversation *convo; + + purple_debug_info(MXIT_PLUGIN_ID, "member_removed: '%s'\n", nickname); + + convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, multimx->roomname, session->acc); + if (convo == NULL) { + purple_debug_error(MXIT_PLUGIN_ID, "Conversation '%s' not found\n", multimx->roomname); + return; + } + + purple_conv_chat_remove_user(PURPLE_CONV_CHAT(convo), nickname, NULL); +} + + +/*------------------------------------------------------------------------ + * Update the full GroupChat member list. + * + * @param session The MXit session object + * @param multimx The MultiMX room object + * @param data The nicknames of the users in the room (separated by \n) + */ +static void member_update(struct MXitSession* session, struct multimx* multimx, char* data) +{ + PurpleConversation *convo; + gchar** userlist; + int i = 0; + + purple_debug_info(MXIT_PLUGIN_ID, "member_update: '%s'\n", data); + + convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, multimx->roomname, session->acc); + if (convo == NULL) { + purple_debug_error(MXIT_PLUGIN_ID, "Conversation '%s' not found\n", multimx->roomname); + return; + } + + /* Clear list */ + purple_conv_chat_clear_users(PURPLE_CONV_CHAT(convo)); + + /* Add each member */ + data = g_strstrip(data); /* string leading & trailing whitespace */ + userlist = g_strsplit(data, "\n", 0); /* tokenize string */ + while (userlist[i] != NULL) { + purple_debug_info(MXIT_PLUGIN_ID, "member_update - adding: '%s'\n", userlist[i]); + purple_conv_chat_add_user(PURPLE_CONV_CHAT(convo), userlist[i], NULL, PURPLE_CBFLAGS_NONE, FALSE); + i++; + } + g_strfreev(userlist); +} + + +/* ------------------------------------------------------------------------------------------------- + * Calls from MXit Protocol layer + * ------------------------------------------------------------------------------------------------- */ + +/*------------------------------------------------------------------------ + * Received a Subscription Request to a MultiMX room. + * + * @param session The MXit session object + * @param contact The invited MultiMX room's contact information + * @param creator The nickname of the room's creator / invitor + */ +void multimx_invite(struct MXitSession* session, struct contact* contact, const char* creator) +{ + GHashTable *components; + struct multimx* multimx = NULL; + + purple_debug_info(MXIT_PLUGIN_ID, "Groupchat invite to '%s' by '%s'\n", contact->alias, creator); + + /* Create a new room */ + multimx = room_create(session, contact->username, contact->alias, STATE_INVITED); + + components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + g_hash_table_insert(components, g_strdup("room"), g_strdup(contact->alias)); + + /* Call libpurple - will trigger either 'mxit_chat_join' or 'mxit_chat_reject' */ + serv_got_chat_invite(session->con, contact->alias, creator, NULL, components); +} + + +/*------------------------------------------------------------------------ + * MultiMX room has been added to the roster. + * + * @param session The MXit session object + * @param contact The MultiMX room's contact information + */ +void multimx_created(struct MXitSession* session, struct contact* contact) +{ + PurpleConnection *gc = session->con; + struct multimx* multimx = NULL; + + purple_debug_info(MXIT_PLUGIN_ID, "Groupchat '%s' created as '%s'\n", contact->alias, contact->username); + + /* Find matching MultiMX group */ + multimx = find_room_by_username(session, contact->username); + if (multimx == NULL) { + multimx = room_create(session, contact->username, contact->alias, TRUE); + } + else if (multimx->state == STATE_INVITED) { + /* After successfully accepting an invitation */ + multimx->state = STATE_JOINED; + } + + /* Call libpurple - will trigger 'mxit_chat_join' */ + serv_got_joined_chat(gc, multimx->chatid, multimx->roomname); + + /* Send ".list" command to GroupChat server to retrieve current member-list */ + mxit_send_message(session, multimx->roomid, ".list", FALSE); +} + + +/*------------------------------------------------------------------------ + * Is this username a MultiMX contact? + * + * @param session The MXit session object + * @param username The username of the contact + * @return TRUE if this contacts matches the RoomID of a MultiMX room. + */ +gboolean is_multimx_contact(struct MXitSession* session, const char* username) +{ + /* Check for username in list of open rooms */ + return (find_room_by_username(session, username) != NULL); +} + + +/*------------------------------------------------------------------------ + * Received a message from a MultiMX room. + * + */ +void multimx_message_received(struct RXMsgData* mx, char* msg, int msglen, short msgtype, int msgflags) +{ + struct multimx* multimx = NULL; + + purple_debug_info(MXIT_PLUGIN_ID, "Groupchat message received: %s\n", msg); + + /* Find matching multimx group */ + multimx = find_room_by_username(mx->session, mx->from); + if (multimx == NULL) { + purple_debug_error(MXIT_PLUGIN_ID, "Groupchat '%s' not found\n", mx->from); + return; + } + + /* Determine if system message or a message from a contact */ + if (msg[0] == '<') { + /* Message contains embedded nickname - must be from contact */ + unsigned int i; + + for (i = 1; i < strlen(msg); i++) { /* search for end of nickname */ + if (msg[i] == '>') { + msg[i] = '\0'; + g_free(mx->from); + mx->from = g_strdup(&msg[1]); + msg = &msg[i+2]; /* skip '>' and newline */ + break; + } + } + + /* now do markup processing on the message */ + mx->chatid = multimx->chatid; + mxit_parse_markup(mx, msg, strlen(msg), msgtype, msgflags); + } + else { + /* Must be a service message */ + char* ofs; + + /* Determine if somebody has joined or left - update member-list */ + if ((ofs = strstr(msg, " has joined")) != NULL) { + /* Somebody has joined */ + *ofs = '\0'; + member_added(mx->session, multimx, msg); + mx->processed = TRUE; + } + else if ((ofs = strstr(msg, " has left")) != NULL) { + /* Somebody has left */ + *ofs = '\0'; + member_removed(mx->session, multimx, msg); + mx->processed = TRUE; + } + else if (g_str_has_prefix(msg, "The following users are in this MultiMx:") == TRUE) { + member_update(mx->session, multimx, msg + strlen("The following users are in this MultiMx:") + 1); + mx->processed = TRUE; + } + else { + /* Display server message in chat window */ + serv_got_chat_in(mx->session->con, multimx->chatid, "MXit", PURPLE_MESSAGE_SYSTEM, msg, mx->timestamp); + mx->processed = TRUE; + } + } +} + + + +/* ------------------------------------------------------------------------------------------------- + * Callbacks from libpurple + * ------------------------------------------------------------------------------------------------- */ + +/*------------------------------------------------------------------------ + * User has selected "Add Chat" from the main menu. + * + * @param gc The connection object + * @return A list of chat configuration values + */ +GList* mxit_chat_info(PurpleConnection *gc) +{ + GList *m = NULL; + struct proto_chat_entry *pce; + + /* Configuration option: Room Name */ + pce = g_new0(struct proto_chat_entry, 1); + pce->label = "_Room Name:"; + pce->identifier = "room"; + pce->required = TRUE; + m = g_list_append(m, pce); + + return m; +} + + +/*------------------------------------------------------------------------ + * User has joined a chatroom, either because they are creating it or they + * accepted an invite. + * + * @param gc The connection object + * @param components The list of chat configuration values + */ +void mxit_chat_join(PurpleConnection *gc, GHashTable *components) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + const char* roomname = NULL; + struct multimx* multimx = NULL; + + purple_debug_info(MXIT_PLUGIN_ID, "mxit_chat_join\n"); + + /* Determine if groupchat already exists */ + roomname = g_hash_table_lookup(components, "room"); + multimx = find_room_by_alias(session, roomname); + + if (multimx != NULL) { + /* The room information already exists */ + + if (multimx->state == STATE_INVITED) { + /* Invite is pending */ + purple_debug_info(MXIT_PLUGIN_ID, "Groupchat %i accept sent\n", multimx->chatid); + + /* Send Subscription Accept to MXit */ + mxit_send_allow_sub(session, multimx->roomid, multimx->roomname); + } + else { + /* Join existing room */ + purple_debug_info(MXIT_PLUGIN_ID, "Groupchat %i rejoined\n", multimx->chatid); + + serv_got_joined_chat(gc, multimx->chatid, multimx->roomname); + } + } + else { + /* Send Groupchat Create to MXit */ + mxit_send_groupchat_create(session, roomname, 0, NULL); + } +} + + +/*------------------------------------------------------------------------ + * User has rejected an invite to join a MultiMX room. + * + * @param gc The connection object + * @param components The list of chat configuration values + */ +void mxit_chat_reject(PurpleConnection *gc, GHashTable* components) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + const char* roomname = NULL; + struct multimx* multimx = NULL; + + purple_debug_info(MXIT_PLUGIN_ID, "mxit_chat_reject\n"); + + roomname = g_hash_table_lookup(components, "room"); + multimx = find_room_by_alias(session, roomname); + if (multimx == NULL) { + purple_debug_error(MXIT_PLUGIN_ID, "Groupchat '%s' not found\n", roomname); + return; + } + + /* Send Subscription Reject to MXit */ + mxit_send_deny_sub(session, multimx->roomid); + + /* Remove from our list of rooms */ + room_remove(session, multimx); +} + + +/*------------------------------------------------------------------------ + * Return name of chatroom (on mouse hover) + * + * @param components The list of chat configuration values. + * @return The name of the chat room + */ +char* mxit_chat_name(GHashTable *components) +{ + return g_strdup(g_hash_table_lookup(components, "room")); +} + + +/*------------------------------------------------------------------------ + * User has selected to invite somebody to a chatroom. + * + * @param gc The connection object + * @param id The chat room ID + * @param msg The invitation message entered by the user + * @param name The username of the person to invite + */ +void mxit_chat_invite(PurpleConnection *gc, int id, const char *msg, const char *username) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + struct multimx* multimx = NULL; + + purple_debug_info(MXIT_PLUGIN_ID, "Groupchat invite to '%s'\n", username); + + /* Find matching MultiMX group */ + multimx = find_room_by_id(session, id); + if (multimx == NULL) { + purple_debug_error(MXIT_PLUGIN_ID, "Could not find groupchat %i\n", id); + return; + } + + /* Send invite to MXit */ + mxit_send_groupchat_invite(session, multimx->roomid, 1, &username); +} + + +/*------------------------------------------------------------------------ + * User as closed the chat window, and the chatroom is not marked as persistent. + * + * @param gc The connection object + * @param id The chat room ID + */ +void mxit_chat_leave(PurpleConnection *gc, int id) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + struct multimx* multimx = NULL; + + purple_debug_info(MXIT_PLUGIN_ID, "Groupchat %i leave\n", id); + + /* Find matching multimx group */ + multimx = find_room_by_id(session, id); + if (multimx == NULL) { + purple_debug_error(MXIT_PLUGIN_ID, "Could not find groupchat %i\n", id); + return; + } + + /* Send Remove Groupchat to MXit */ + mxit_send_remove(session, multimx->roomid); + + /* Remove from our list of rooms */ + room_remove(session, multimx); +} + + +/*------------------------------------------------------------------------ + * User has entered a message in a chatroom window, send it to the MXit server. + * + * @param gc The connection object + * @param id The chat room ID + * @param message The sent message data + * @param flags The message flags + * @return Indicates success / failure + */ +int mxit_chat_send(PurpleConnection *gc, int id, const char *message, PurpleMessageFlags flags) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + struct multimx* multimx = NULL; + const char* nickname; + + purple_debug_info(MXIT_PLUGIN_ID, "Groupchat %i message send: '%s'\n", id, message); + + /* Find matching MultiMX group */ + multimx = find_room_by_id(session, id); + if (multimx == NULL) { + purple_debug_error(MXIT_PLUGIN_ID, "Could not find groupchat %i\n", id); + return -1; + } + + /* Send packet to MXit */ + mxit_send_message(session, multimx->roomid, message, TRUE); + + /* Determine our nickname to display */ + if (session->profile && (session->profile->nickname[0] != '\0')) /* default is profile name (since that's what everybody else sees) */ + nickname = session->profile->nickname; + else + nickname = purple_account_get_alias(purple_connection_get_account(gc)); /* local alias */ + + /* Display message in chat window */ + serv_got_chat_in(gc, id, nickname, flags, message, time(NULL)); + + return 0; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/multimx.h Mon Nov 09 19:27:45 2009 +0000 @@ -0,0 +1,104 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- MultiMx GroupChat -- + * + * Andrew Victor <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef _MXIT_MULTIMX_H_ +#define _MXIT_MULTIMX_H_ + +#include "roster.h" + + +/* GroupChat Room state */ +#define STATE_CREATOR 0 +#define STATE_INVITED 1 +#define STATE_JOINED 2 + +/* + * a MultiMX room + */ +struct multimx { + char roomname[MXIT_CP_MAX_ALIAS_LEN]; /* name of the room */ + char roomid[MXIT_CP_MAX_JID_LEN]; /* internal JID for room */ + int chatid; /* libpurple chat ID */ + short state; /* state */ +}; + + +/* + * Received a Subscription Request to a MultiMX room. + */ +void multimx_invite(struct MXitSession* session, struct contact* contact, const char* creator); + +/* + * MultiMX room has been added to the roster. + */ +void multimx_created(struct MXitSession* session, struct contact* contact); + +/* + * Is this username a MultiMX contact? + */ +gboolean is_multimx_contact(struct MXitSession* session, const char* username); + +/* + * Received a message from a MultiMX room. + */ +void multimx_message_received(struct RXMsgData* mx, char* message, int len, short msgtype, int msgflags); + +/* + * User has selected "Add Chat" from the main menu. + */ +GList* mxit_chat_info(PurpleConnection *gc); + +/* + * User has joined a chatroom, either because they are creating it or they accepted an invite. + */ +void mxit_chat_join(PurpleConnection *gc, GHashTable *data); + +/* + * User has rejected an invite to join a MultiMX room. + */ +void mxit_chat_reject(PurpleConnection *gc, GHashTable* components); + +/* + * Return name of chatroom (on mouse hover) + */ +char* mxit_chat_name(GHashTable *data); + +/* + * User has selected to invite somebody to a chatroom. + */ +void mxit_chat_invite(PurpleConnection *gc, int id, const char *msg, const char *name); + +/* + * User as closed the chat window, and the chatroom is not marked as persistent. + */ +void mxit_chat_leave(PurpleConnection *gc, int id); + +/* + * User has entered a message in a chatroom window, send it to the MXit server. + */ +int mxit_chat_send(PurpleConnection *gc, int id, const char *message, PurpleMessageFlags flags); + + +#endif /* _MXIT_MULTIMX_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/mxit.c Mon Nov 09 19:27:45 2009 +0000 @@ -0,0 +1,694 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- MXit libPurple plugin API -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include <glib.h> +#include <stdio.h> +#include <string.h> + +#include "purple.h" +#include "notify.h" +#include "plugin.h" +#include "version.h" + +#include "mxit.h" +#include "protocol.h" +#include "login.h" +#include "roster.h" +#include "chunk.h" +#include "filexfer.h" +#include "actions.h" +#include "multimx.h" + + +#ifdef MXIT_LINK_CLICK + + +/* pidgin callback function pointers for URI click interception */ +static void *(*mxit_pidgin_uri_cb)(const char *uri); +static PurpleNotifyUiOps* mxit_nots_override_original; +static PurpleNotifyUiOps mxit_nots_override; +static int not_link_ref_count = 0; + + +/*------------------------------------------------------------------------ + * Handle an URI clicked on the UI + * + * @param link the link name which has been clicked + */ +static void* mxit_link_click( const char* link64 ) +{ + PurpleAccount* account; + PurpleConnection* con; + gchar** parts = NULL; + gchar* link = NULL; + unsigned int len; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_link_click (%s)\n", link64 ); + + if ( g_ascii_strncasecmp( link64, MXIT_LINK_PREFIX, strlen( MXIT_LINK_PREFIX ) ) != 0 ) { + /* this is not for us */ + goto skip; + } + + /* decode the base64 payload */ + link = (gchar*) purple_base64_decode( link64 + strlen( MXIT_LINK_PREFIX ), &len ); + purple_debug_info( MXIT_PLUGIN_ID, "Clicked Link: '%s'\n", link ); + + parts = g_strsplit( link, "|", 5 ); + + /* check if this is a valid mxit link */ + if ( ( !parts ) || ( !parts[0] ) || ( !parts[1] ) || ( !parts[2] ) || ( !parts[3] ) || ( !parts[4] ) ) { + /* this is not for us */ + goto skip; + } + else if ( g_ascii_strcasecmp( parts[0], MXIT_LINK_KEY ) != 0 ) { + /* this is not for us */ + goto skip; + } + + /* find the account */ + account = purple_accounts_find( parts[1], parts[2] ); + if ( !account ) + goto skip; + con = purple_account_get_connection( account ); + + /* send click message back to MXit */ + mxit_send_message( con->proto_data, parts[3], parts[4], FALSE ); + + g_free( link ); + link = NULL; + g_strfreev( parts ); + parts = NULL; + + return (void*) link64; + +skip: + /* this is not an internal mxit link */ + + if ( link ) + g_free( link ); + link = NULL; + + if ( parts ) + g_strfreev( parts ); + parts = NULL; + + if ( mxit_pidgin_uri_cb ) + return mxit_pidgin_uri_cb( link64 ); + else + return (void*) link64; +} + + +/*------------------------------------------------------------------------ + * Register MXit to receive URI click notifications from the UI + */ +void mxit_register_uri_handler() +{ + not_link_ref_count++; + if ( not_link_ref_count == 1 ) { + /* make copy of notifications */ + mxit_nots_override_original = purple_notify_get_ui_ops(); + memcpy( &mxit_nots_override, mxit_nots_override_original, sizeof( PurpleNotifyUiOps ) ); + + /* save previously configured callback function pointer */ + mxit_pidgin_uri_cb = mxit_nots_override.notify_uri; + + /* override the URI function call with MXit's own one */ + mxit_nots_override.notify_uri = mxit_link_click; + purple_notify_set_ui_ops( &mxit_nots_override ); + } +} + + +/*------------------------------------------------------------------------ + * Unegister MXit from receiving URI click notifications from the UI + */ +static void mxit_unregister_uri_handler() +{ + not_link_ref_count--; + if ( not_link_ref_count == 0 ) { + /* restore the notifications to its original state */ + purple_notify_set_ui_ops( mxit_nots_override_original ); + } +} + +#endif + + +/*------------------------------------------------------------------------ + * This gets called when a new chat conversation is opened by the user + * + * @param conv The conversation object + * @param session The MXit session object + */ +static void mxit_cb_chat_created( PurpleConversation* conv, struct MXitSession* session ) +{ + PurpleConnection* gc; + struct contact* contact; + PurpleBuddy* buddy; + const char* who; + + gc = purple_conversation_get_gc( conv ); + if ( session->con != gc ) { + /* not our conversation */ + return; + } + else if ( purple_conversation_get_type( conv ) != PURPLE_CONV_TYPE_IM ) { + /* wrong type of conversation */ + return; + } + + /* get the contact name */ + who = purple_conversation_get_name( conv ); + if ( !who ) + return; + + purple_debug_info( MXIT_PLUGIN_ID, "Conversation started with '%s'\n", who ); + + /* find the buddy object */ + buddy = purple_find_buddy( session->acc, who ); + if ( ( !buddy ) || ( !buddy->proto_data ) ) + return; + + /* we ignore all conversations with which we have chatted with in this session */ + if ( find_active_chat( session->active_chats, who ) ) + return; + + /* determite if this buddy is a MXit service */ + contact = buddy->proto_data; + switch ( contact->type ) { + case MXIT_TYPE_BOT : + case MXIT_TYPE_CHATROOM : + case MXIT_TYPE_GALLERY : + case MXIT_TYPE_INFO : + serv_got_im( session->con, who, "<font color=\"#999999\">Loading menu...</font>\n", PURPLE_MESSAGE_NOTIFY, time( NULL ) ); + mxit_send_message( session, who, " ", FALSE ); + default : + break; + } +} + + +/*------------------------------------------------------------------------ + * Enable some signals to handled by our plugin + * + * @param session The MXit session object + */ +void mxit_enable_signals( struct MXitSession* session ) +{ + /* enable the signal when a new conversation is opened by the user */ + purple_signal_connect_priority( purple_conversations_get_handle(), "conversation-created", session, PURPLE_CALLBACK( mxit_cb_chat_created ), + session, PURPLE_SIGNAL_PRIORITY_HIGHEST ); +} + + +/*------------------------------------------------------------------------ + * Disable some signals handled by our plugin + * + * @param session The MXit session object + */ +static void mxit_disable_signals( struct MXitSession* session ) +{ + /* disable the signal when a new conversation is opened by the user */ + purple_signal_disconnect( purple_conversations_get_handle(), "conversation-created", session, PURPLE_CALLBACK( mxit_cb_chat_created ) ); +} + + +/*------------------------------------------------------------------------ + * Return the base icon name. + * + * @param account The MXit account object + * @param buddy The buddy + * @return The icon name (excluding extension) + */ +static const char* mxit_list_icon( PurpleAccount* account, PurpleBuddy* buddy ) +{ + return "mxit"; +} + + +/*------------------------------------------------------------------------ + * Return the emblem icon name. + * + * @param buddy The buddy + * @return The icon name (excluding extension) + */ +static const char* mxit_list_emblem( PurpleBuddy* buddy ) +{ + struct contact* contact = buddy->proto_data; + + if ( !contact ) + return NULL; + + switch ( contact-> type ) { + case MXIT_TYPE_JABBER : /* external contacts via MXit */ + case MXIT_TYPE_MSN : + case MXIT_TYPE_YAHOO : + case MXIT_TYPE_ICQ : + case MXIT_TYPE_AIM : + case MXIT_TYPE_QQ : + case MXIT_TYPE_WV : + return "external"; + + case MXIT_TYPE_BOT : /* MXit services */ + case MXIT_TYPE_GALLERY : + case MXIT_TYPE_INFO : + return "bot"; + + case MXIT_TYPE_CHATROOM : /* MXit group chat services */ + case MXIT_TYPE_MULTIMX : + default: + return NULL; + } +} + + +/*------------------------------------------------------------------------ + * Return short string representing buddy's status for display on buddy list. + * Returns status message (if one is set), or otherwise the mood. + * + * @param buddy The buddy. + * @return The status text + */ +char* mxit_status_text( PurpleBuddy* buddy ) +{ + struct contact* contact = buddy->proto_data; + + if ( !contact ) + return NULL; + + if ( contact->statusMsg ) { + /* status message */ + return g_strdup( contact-> statusMsg ); + } + else { + /* mood */ + return g_strdup( mxit_convert_mood_to_name( contact->mood ) ); + } +} + + +/*------------------------------------------------------------------------ + * Return UI tooltip information for a buddy when hovering in buddy list. + * + * @param buddy The buddy + * @param info The tooltip info being returned + * @param full Return full or summarized information + */ +static void mxit_tooltip( PurpleBuddy* buddy, PurpleNotifyUserInfo* info, gboolean full ) +{ + struct contact* contact = buddy->proto_data; + + if ( !contact ) + return; + + /* status (reference: "libpurple/notify.h") */ + if ( contact->presence != MXIT_PRESENCE_OFFLINE ) + purple_notify_user_info_add_pair( info, _( "Status" ), mxit_convert_presence_to_name( contact->presence ) ); + + /* status message */ + if ( contact->statusMsg ) + purple_notify_user_info_add_pair( info, _( "Status Message" ), contact->statusMsg ); + + /* mood */ + if ( contact->mood != MXIT_MOOD_NONE ) + purple_notify_user_info_add_pair( info, _( "Mood" ), mxit_convert_mood_to_name( contact->mood ) ); + + /* subscription type */ + if ( contact->subtype != 0 ) + purple_notify_user_info_add_pair( info, _( "Subscription" ), mxit_convert_subtype_to_name( contact->subtype ) ); + + /* hidden number */ + if ( contact->flags & MXIT_CFLAG_HIDDEN ) + purple_notify_user_info_add_pair( info, _( "Hidden Number" ), "Yes" ); +} + + +/*------------------------------------------------------------------------ + * Initiate the logout sequence, close the connection and clear the session data. + * + * @param gc The connection object + */ +static void mxit_close( PurpleConnection* gc ) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + + /* disable signals */ + mxit_disable_signals( session ); + + /* close the connection */ + mxit_close_connection( session ); + +#ifdef MXIT_LINK_CLICK + /* unregister for uri click notification */ + mxit_unregister_uri_handler(); +#endif + + purple_debug_info( MXIT_PLUGIN_ID, "Releasing the session object..\n" ); + + /* free the session memory */ + g_free( session ); + session = NULL; +} + + +/*------------------------------------------------------------------------ + * Send a message to a contact + * + * @param gc The connection object + * @param who The username of the recipient + * @param message The message text + * @param flags Message flags (defined in conversation.h) + * @return Positive value (success, and echo to conversation window) + Zero (success, no echo) + Negative value (error) + */ +static int mxit_send_im( PurpleConnection* gc, const char* who, const char* message, PurpleMessageFlags flags ) +{ + purple_debug_info( MXIT_PLUGIN_ID, "Sending message '%s' to buddy '%s'\n", message, who ); + + mxit_send_message( gc->proto_data, who, message, TRUE ); + + return 1; /* echo to conversation window */ +} + + +/*------------------------------------------------------------------------ + * The user changed their current presence state. + * + * @param account The MXit account object + * @param status The new status (libPurple status type) + */ +static void mxit_set_status( PurpleAccount* account, PurpleStatus* status ) +{ + struct MXitSession* session = purple_account_get_connection( account )->proto_data; + const char* statusid; + int presence; + char* statusmsg1; + char* statusmsg2; + + /* get the status id (reference: "libpurple/status.h") */ + statusid = purple_status_get_id( status ); + + /* convert the purple status to a mxit status */ + presence = mxit_convert_presence( statusid ); + if ( presence < 0 ) { + /* error, status not found */ + purple_debug_info( MXIT_PLUGIN_ID, "Presence status NOT found! (id = %s)\n", statusid ); + return; + } + + statusmsg1 = purple_markup_strip_html( purple_status_get_attr_string( status, "message" ) ); + statusmsg2 = g_strndup( statusmsg1, CP_MAX_STATUS_MSG ); + purple_debug_info( MXIT_PLUGIN_ID, "mxit_set_status: '%s'\n", statusmsg2 ); + + /* update presence state */ + mxit_send_presence( session, presence, statusmsg2 ); + + g_free( statusmsg1 ); + g_free( statusmsg2 ); +} + + +/*------------------------------------------------------------------------ + * MXit supports messages to offline contacts. + * + * @param buddy The buddy + */ +static gboolean mxit_offline_message( const PurpleBuddy *buddy ) +{ + return TRUE; +} + + +/*------------------------------------------------------------------------ + * Free the resources used to store a buddy. + * + * @param buddy The buddy + */ +static void mxit_free_buddy( PurpleBuddy* buddy ) +{ + struct contact* contact; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_free_buddy\n" ); + + contact = buddy->proto_data; + if ( contact ) { + if ( contact->statusMsg ) + g_free( contact->statusMsg ); + if ( contact->avatarId ) + g_free( contact->avatarId ); + g_free( contact ); + } + buddy->proto_data = NULL; +} + + +/*------------------------------------------------------------------------ + * Periodic task called every KEEPALIVE_INTERVAL (30 sec) to to maintain + * idle connections, timeouts and the transmission queue to the MXit server. + * + * @param gc The connection object + */ +static void mxit_keepalive( PurpleConnection *gc ) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + + /* if not logged in, there is nothing to do */ + if ( !( session->flags & MXIT_FLAG_LOGGEDIN ) ) + return; + + /* pinging is only for socket connections (HTTP does polling) */ + if ( session->http ) + return; + + if ( session->last_tx <= time( NULL ) - MXIT_PING_INTERVAL ) { + /* + * this connection has been idle for too long, better ping + * the server before it kills our connection. + */ + mxit_send_ping( session ); + } +} + + +/*------------------------------------------------------------------------ + * Set or clear our Buddy icon. + * + * @param gc The connection object + * @param img The buddy icon data + */ +static void mxit_set_buddy_icon( PurpleConnection *gc, PurpleStoredImage *img ) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + + if ( img == NULL ) + mxit_set_avatar( session, NULL, 0 ); + else + mxit_set_avatar( session, purple_imgstore_get_data( img ), purple_imgstore_get_size( img ) ); +} + + +/*------------------------------------------------------------------------ + * Request profile information for another MXit contact. + * + * @param gc The connection object + * @param who The username of the contact. + */ +static void mxit_get_info( PurpleConnection *gc, const char *who ) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + const char* profilelist[] = { CP_PROFILE_BIRTHDATE, CP_PROFILE_GENDER, CP_PROFILE_HIDENUMBER, CP_PROFILE_FULLNAME, + CP_PROFILE_TITLE, CP_PROFILE_FIRSTNAME, CP_PROFILE_LASTNAME, CP_PROFILE_EMAIL }; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_get_info: '%s'\n", who ); + + + /* send profile request */ + mxit_send_extprofile_request( session, who, ARRAY_SIZE( profilelist ), profilelist ); +} + + +/*------------------------------------------------------------------------ + * Return a list of labels to be used by Pidgin for assisting the user. + */ +static GHashTable* mxit_get_text_table( PurpleAccount* acc ) +{ + GHashTable* table; + + table = g_hash_table_new( g_str_hash, g_str_equal ); + + g_hash_table_insert( table, "login_label", _( "Your Mobile Number..." ) ); + + return table; +} + +/*========================================================================================================================*/ + +static PurplePluginProtocolInfo proto_info = { + OPT_PROTO_REGISTER_NOSCREENNAME | OPT_PROTO_UNIQUE_CHATNAME | OPT_PROTO_IM_IMAGE, /* options */ + NULL, /* user_splits */ + NULL, /* protocol_options */ + { /* icon_spec */ + "png", /* format */ + 32, 32, /* min width & height */ + MXIT_AVATAR_SIZE, /* max width */ + MXIT_AVATAR_SIZE, /* max height */ + 100000, /* max filezize */ + PURPLE_ICON_SCALE_SEND | PURPLE_ICON_SCALE_DISPLAY /* scaling rules */ + }, + mxit_list_icon, /* list_icon */ + mxit_list_emblem, /* list_emblem */ + mxit_status_text, /* status_text */ + mxit_tooltip, /* tooltip_text */ + mxit_status_types, /* status types [roster.c] */ + NULL, /* blist_node_menu */ + mxit_chat_info, /* chat_info [multimx.c] */ + NULL, /* chat_info_defaults */ + mxit_login, /* login [login.c] */ + mxit_close, /* close */ + mxit_send_im, /* send_im */ + NULL, /* set_info */ + NULL, /* send_typing */ + mxit_get_info, /* get_info */ + mxit_set_status, /* set_status */ + NULL, /* set_idle */ + NULL, /* change_passwd */ + mxit_add_buddy, /* add_buddy [roster.c] */ + NULL, /* add_buddies */ + mxit_remove_buddy, /* remove_buddy [roster.c] */ + NULL, /* remove_buddies */ + NULL, /* add_permit */ + NULL, /* add_deny */ + NULL, /* rem_permit */ + NULL, /* rem_deny */ + NULL, /* set_permit_deny */ + mxit_chat_join, /* join_chat [multimx.c] */ + mxit_chat_reject, /* reject chat invite [multimx.c] */ + mxit_chat_name, /* get_chat_name [multimx.c] */ + mxit_chat_invite, /* chat_invite [multimx.c] */ + mxit_chat_leave, /* chat_leave [multimx.c] */ + NULL, /* chat_whisper */ + mxit_chat_send, /* chat_send [multimx.c] */ + mxit_keepalive, /* keepalive */ + mxit_register, /* register_user */ + NULL, /* get_cb_info */ + NULL, /* get_cb_away */ + mxit_buddy_alias, /* alias_buddy [roster.c] */ + mxit_buddy_group, /* group_buddy [roster.c] */ + mxit_rename_group, /* rename_group [roster.c] */ + mxit_free_buddy, /* buddy_free */ + NULL, /* convo_closed */ + NULL, /* normalize */ + mxit_set_buddy_icon, /* set_buddy_icon */ + NULL, /* remove_group */ // TODO: Add function to move all contacts out of this group (cmd=30 - remove group)? + NULL, /* get_cb_real_name */ + NULL, /* set_chat_topic */ + NULL, /* find_blist_chat */ + NULL, /* roomlist_get_list */ + NULL, /* roomlist_cancel */ + NULL, /* roomlist_expand_category */ + mxit_xfer_enabled, /* can_receive_file [filexfer.c] */ + mxit_xfer_tx, /* send_file [filexfer.c */ + mxit_xfer_new, /* new_xfer [filexfer.c] */ + mxit_offline_message, /* offline_message */ + NULL, /* whiteboard_prpl_ops */ + NULL, /* send_raw */ + NULL, /* roomlist_room_serialize */ + NULL, /* unregister_user */ + NULL, /* send_attention */ + NULL, /* attention_types */ + sizeof( PurplePluginProtocolInfo ), /* struct_size */ + mxit_get_text_table, /* get_account_text_table */ + NULL, + NULL +}; + + +static PurplePluginInfo plugin_info = { + PURPLE_PLUGIN_MAGIC, /* purple magic, this must always be PURPLE_PLUGIN_MAGIC */ + PURPLE_MAJOR_VERSION, /* libpurple version */ + PURPLE_MINOR_VERSION, /* libpurple version */ + PURPLE_PLUGIN_PROTOCOL, /* plugin type (connecting to another network) */ + NULL, /* UI requirement (NULL for core plugin) */ + 0, /* plugin flags (zero is default) */ + NULL, /* plugin dependencies (set this value to NULL no matter what) */ + PURPLE_PRIORITY_DEFAULT, /* libpurple priority */ + + MXIT_PLUGIN_ID, /* plugin id (must be unique) */ + MXIT_PLUGIN_NAME, /* plugin name (this will be displayed in the UI) */ + MXIT_PLUGIN_VERSION, /* version of the plugin */ + + MXIT_PLUGIN_SUMMARY, /* short summary of the plugin */ + MXIT_PLUGIN_DESC, /* description of the plugin (can be long) */ + MXIT_PLUGIN_EMAIL, /* plugin author name and email address */ + MXIT_PLUGIN_WWW, /* plugin website (to find new versions and reporting of bugs) */ + + NULL, /* function pointer for loading the plugin */ + NULL, /* function pointer for unloading the plugin */ + NULL, /* function pointer for destroying the plugin */ + + NULL, /* pointer to an UI-specific struct */ + &proto_info, /* pointer to either a PurplePluginLoaderInfo or PurplePluginProtocolInfo struct */ + NULL, /* pointer to a PurplePluginUiInfo struct */ + mxit_actions, /* function pointer where you can define plugin-actions */ + + /* padding */ + NULL, /* pointer reserved for future use */ + NULL, /* pointer reserved for future use */ + NULL, /* pointer reserved for future use */ + NULL /* pointer reserved for future use */ +}; + + +/*------------------------------------------------------------------------ + * Initialising the MXit plugin. + * + * @param plugin The plugin object + */ +static void init_plugin( PurplePlugin* plugin ) +{ + PurpleAccountOption* option; + + purple_debug_info( MXIT_PLUGIN_ID, "Loading MXit libPurple plugin...\n" ); + + /* Configuration options */ + + /* WAP server (reference: "libpurple/accountopt.h") */ + option = purple_account_option_string_new( _( "WAP Server" ), MXIT_CONFIG_WAPSERVER, DEFAULT_WAPSITE ); + proto_info.protocol_options = g_list_append( proto_info.protocol_options, option ); + + option = purple_account_option_bool_new( _( "Connect via HTTP" ), MXIT_CONFIG_USE_HTTP, FALSE ); + proto_info.protocol_options = g_list_append( proto_info.protocol_options, option ); + + option = purple_account_option_bool_new( _( "Enable splash-screen popup" ), MXIT_CONFIG_SPLASHPOPUP, FALSE ); + proto_info.protocol_options = g_list_append( proto_info.protocol_options, option ); + + g_assert( sizeof( struct raw_chunk ) == 5 ); +} + +PURPLE_INIT_PLUGIN( mxit, init_plugin, plugin_info ); +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/mxit.h Mon Nov 09 19:27:45 2009 +0000 @@ -0,0 +1,197 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- MXit libPurple plugin API -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef _MXIT_H_ +#define _MXIT_H_ + + +/* internationalize feedback strings */ +#ifndef _ +#ifdef GETTEXT_PACKAGE +#include <glib/gi18n-lib.h> +#else +#define _( x ) ( x ) +#endif +#endif + + +#if defined( __APPLE__ ) +/* apple architecture */ +#ifndef HOST_NAME_MAX +#define HOST_NAME_MAX 512 +#endif +#elif defined( _WIN32 ) +/* windows architecture */ +#define HOST_NAME_MAX 512 +#include "libc_interface.h" +#elif defined( __linux__ ) +/* linux architecture */ +#include <net/if.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#else +/* other architecture */ +#ifndef HOST_NAME_MAX +#define HOST_NAME_MAX 512 +#endif +#endif + + +#include "protocol.h" +#include "profile.h" + + +/* Plugin details */ +#define MXIT_PLUGIN_ID "prpl-loubserp-mxit" +#define MXIT_PLUGIN_NAME "MXit" +#define MXIT_PLUGIN_VERSION "2.2.0" +#define MXIT_PLUGIN_EMAIL "Pieter Loubser <libpurple@mxit.com>" +#define MXIT_PLUGIN_WWW "http://www.mxit.com" +#define MXIT_PLUGIN_SUMMARY "MXit Protocol Plugin" +#define MXIT_PLUGIN_DESC "MXit" + +#define MXIT_HTTP_USERAGENT "libpurple-"MXIT_PLUGIN_VERSION + + +/* default connection settings */ +#define DEFAULT_SERVER "stream.mxit.co.za" +#define DEFAULT_PORT 9119 +#define DEFAULT_WAPSITE "http://www.mxit.com" +#define DEFAULT_HTTP_SERVER "http://int.poll.mxit.com:80/mxit" + + +/* Purple account configuration variable names */ +#define MXIT_CONFIG_STATE "state" +#define MXIT_CONFIG_WAPSERVER "wap_server" +#define MXIT_CONFIG_DISTCODE "distcode" +#define MXIT_CONFIG_CLIENTKEY "clientkey" +#define MXIT_CONFIG_DIALCODE "dialcode" +#define MXIT_CONFIG_SERVER_ADDR "server" +#define MXIT_CONFIG_SERVER_PORT "port" +#define MXIT_CONFIG_HTTPSERVER "httpserver" +#define MXIT_CONFIG_SPLASHID "splashid" +#define MXIT_CONFIG_SPLASHCLICK "splashclick" +#define MXIT_CONFIG_SPLASHPOPUP "splashpopup" +#define MXIT_CONFIG_COUNTRYCODE "cc" +#define MXIT_CONFIG_LOCALE "locale" +#define MXIT_CONFIG_USE_HTTP "use_http" + + +/* account states */ +#define MXIT_STATE_LOGIN 0x00 +#define MXIT_STATE_REGISTER1 0x01 +#define MXIT_STATE_REGISTER2 0x02 + + +/* Client session flags */ +#define MXIT_FLAG_CONNECTED 0x01 /* established connection to the server */ +#define MXIT_FLAG_LOGGEDIN 0x02 /* user currently logged in */ +#define MXIT_FLAG_FIRSTROSTER 0x04 /* set to true once the first roster update has been recevied and processed */ + + +/* define this to enable the link clicking support */ +#define MXIT_LINK_CLICK + + +#ifdef MXIT_LINK_CLICK +#define MXIT_LINK_PREFIX "gopher://" +#define MXIT_LINK_KEY "MXIT" +#endif + + +#define ARRAY_SIZE( x ) ( sizeof( x ) / sizeof( x[0] ) ) + + +/* + * data structure containing all MXit session information + */ +struct MXitSession { + /* socket connection */ + char server[HOST_NAME_MAX]; /* MXit server name to connect to */ + int port; /* MXit server port to connect on */ + int fd; /* connection file descriptor */ + + /* http connection */ + gboolean http; /* connect to MXit via HTTP and not by socket */ + char http_server[HOST_NAME_MAX]; /* MXit HTTP server */ + unsigned int http_sesid; /* HTTP session id */ + unsigned int http_seqno; /* HTTP request sequence number */ + guint http_timer_id; /* timer resource id (pidgin) */ + int http_interval; /* poll inverval */ + time_t http_last_poll; /* the last time a poll has been sent */ + guint http_handler; /* HTTP connection handler */ + void* http_out_req; /* HTTP outstanding request */ + + /* client */ + struct login_data* logindata; + char* encpwd; /* encrypted password */ + char distcode[64]; /* distribution code */ + char clientkey[16]; /* client key */ + char dialcode[8]; /* dialing code */ + short flags; /* client session flags (see above) */ + + /* personal (profile) */ + struct MXitProfile* profile; /* user's profile information */ + int mood; /* user's current mood */ + + /* libpurple */ + PurpleAccount* acc; /* pointer to the libpurple internal account struct */ + PurpleConnection* con; /* pointer to the libpurple internal connection struct */ + + /* transmit */ + struct tx_queue queue; /* transmit packet queue (FIFO mode) */ + time_t last_tx; /* timestamp of last packet sent */ + int outack; /* outstanding ack packet */ + guint q_timer; /* timer handler for managing queue */ + + /* receive */ + char rx_lbuf[16]; /* receive byte buffer (socket packet length) */ + char rx_dbuf[CP_MAX_PACKET]; /* receive byte buffer (raw data) */ + unsigned int rx_i; /* receive buffer current index */ + int rx_res; /* amount of bytes still outstanding for the current packet */ + char rx_state; /* current receiver state */ + time_t last_rx; /* timestamp of last packet received */ + GList* active_chats; /* list of all our contacts we received messages from (active chats) */ + + /* groupchat */ + GList* rooms; /* active groupchat rooms */ + + /* inline images */ + GHashTable* iimages; /* table which maps inline images (including emoticons) to purple's imgstore id's */ +}; + + +char* mxit_status_text( PurpleBuddy* buddy ); +void mxit_enable_signals( struct MXitSession* session ); + +#ifdef MXIT_LINK_CLICK +void mxit_register_uri_handler(); +#endif + + +#endif /* _MXIT_H_ */ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/profile.c Mon Nov 09 19:27:45 2009 +0000 @@ -0,0 +1,160 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- user profile's -- + * + * Andrew Victor <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include <ctype.h> +#include <string.h> + +#include "purple.h" + +#include "mxit.h" +#include "profile.h" +#include "roster.h" + + +/*------------------------------------------------------------------------ + * Returns true if it is a valid date. + * + * @param bday Date-of-Birth string + * @return TRUE if valid, else FALSE + */ +gboolean validateDate( const char* bday ) +{ + struct tm* tm; + time_t t; + int cur_year; + int max_days[13] = { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + char date[16]; + int year; + int month; + int day; + + /* validate length */ + if ( strlen( bday ) != 10 ) { + return FALSE; + } + + /* validate the format */ + if ( ( !isdigit( bday[0] ) ) || ( !isdigit( bday[1] ) ) || ( !isdigit( bday[2] ) ) || ( !isdigit( bday[3] ) ) || /* year */ + ( bday[4] != '-' ) || + ( !isdigit( bday[5] ) ) || ( !isdigit( bday[6] ) ) || /* month */ + ( bday[7] != '-' ) || + ( !isdigit( bday[8] ) ) || ( !isdigit( bday[9] ) ) ) { /* day */ + return FALSE; + } + + /* convert */ + t = time( NULL ); + tm = gmtime( &t ); + cur_year = tm->tm_year + 1900; + memcpy( date, bday, 10 ); + date[4] = '\0'; + date[7] = '\0'; + date[10] = '\0'; + year = atoi( &date[0] ); + month = atoi( &date[5] ); + day = atoi( &date[8] ); + + /* validate month */ + if ( ( month < 1 ) || ( month > 12 ) ) { + return FALSE; + } + + /* validate day */ + if ( ( day < 1 ) || ( day > max_days[month] ) ) { + return FALSE; + } + + /* validate year */ + if ( ( year < ( cur_year - 100 ) ) || ( year >= cur_year ) ) { + /* you are either tooo old or tooo young to join mxit... sorry */ + return FALSE; + } + + /* special case leap-year */ + if ( ( year % 4 != 0 ) && ( month == 2 ) && ( day == 29 ) ) { + /* cannot have 29 days in February in non leap-years! */ + return FALSE; + } + + return TRUE; +} + + +/*------------------------------------------------------------------------ + * Display the profile information. + * + * @param session The MXit session object + * @param username The username who's profile information this is + * @param profile The profile + */ +void mxit_show_profile( struct MXitSession* session, const char* username, struct MXitProfile* profile ) +{ + PurpleNotifyUserInfo* info = purple_notify_user_info_new(); + struct contact* contact = NULL; + PurpleBuddy* buddy; + + buddy = purple_find_buddy( session->acc, username ); + if ( buddy ) { + purple_notify_user_info_add_pair( info, _( "Alias" ), buddy->alias ); + purple_notify_user_info_add_section_break( info ); + contact = buddy->proto_data; + } + + purple_notify_user_info_add_pair( info, _( "Nick Name" ), profile->nickname ); + purple_notify_user_info_add_pair( info, _( "Birthday" ), profile->birthday ); + purple_notify_user_info_add_pair( info, _( "Gender" ), profile->male ? _( "Male" ) : _( "Female" ) ); + purple_notify_user_info_add_pair( info, _( "Hidden Number" ), profile->hidden ? _( "Yes" ) : _( "No" ) ); + + purple_notify_user_info_add_section_break( info ); + + /* optional information */ + purple_notify_user_info_add_pair( info, _( "Title" ), profile->title ); + purple_notify_user_info_add_pair( info, _( "First Name" ), profile->firstname ); + purple_notify_user_info_add_pair( info, _( "Last Name" ), profile->lastname ); + purple_notify_user_info_add_pair( info, _( "Email" ), profile->email ); + + purple_notify_user_info_add_section_break( info ); + + if ( contact ) { + /* presence */ + purple_notify_user_info_add_pair( info, _( "Status" ), mxit_convert_presence_to_name( contact->presence ) ); + + /* mood */ + if ( contact->mood != MXIT_MOOD_NONE ) + purple_notify_user_info_add_pair( info, _( "Mood" ), mxit_convert_mood_to_name( contact->mood ) ); + else + purple_notify_user_info_add_pair( info, _( "Mood" ), _( "None" ) ); + + /* status message */ + if ( contact->statusMsg ) + purple_notify_user_info_add_pair( info, _( "Status Message" ), contact->statusMsg ); + + /* subscription type */ + purple_notify_user_info_add_pair( info, _( "Subscription" ), mxit_convert_subtype_to_name( contact->subtype ) ); + } + + purple_notify_userinfo( session->con, username, info, NULL, NULL ); + purple_notify_user_info_destroy( info ); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/profile.h Mon Nov 09 19:27:45 2009 +0000 @@ -0,0 +1,56 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- user profile's -- + * + * Andrew Victor <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef _MXIT_PROFILE_H_ +#define _MXIT_PROFILE_H_ + +#include <glib.h> + + +struct MXitProfile { + /* required */ + char loginname[64]; /* name user uses to log into MXit with (aka 'mxitid') */ + char nickname[64]; /* user's own display name (aka 'nickname', aka 'fullname', aka 'alias') in MXit */ + char birthday[16]; /* user's birthday "YYYY-MM-DD" */ + gboolean male; /* true if the user's gender is male (otherwise female) */ + char pin[16]; /* user's password */ + + /* optional */ + char title[32]; /* user's title */ + char firstname[64]; /* user's first name */ + char lastname[64]; /* user's last name (aka 'surname') */ + char email[64]; /* user's email address */ + char mobilenr[21]; /* user's mobile number */ + + gboolean hidden; /* set if the user's msisdn should remain hidden */ +}; + +struct MXitSession; +void mxit_show_profile( struct MXitSession* session, const char* username, struct MXitProfile* profile ); + +gboolean validateDate( const char* bday ); + + +#endif /* _MXIT_PROFILE_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/protocol.c Mon Nov 09 19:27:45 2009 +0000 @@ -0,0 +1,2442 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- MXit client protocol implementation -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> + +#include "purple.h" + +#include "protocol.h" +#include "mxit.h" +#include "roster.h" +#include "chunk.h" +#include "filexfer.h" +#include "markup.h" +#include "multimx.h" +#include "splashscreen.h" +#include "login.h" +#include "formcmds.h" +#include "http.h" + + +#define MXIT_MS_OFFSET 3 + +/* configure the right record terminator char to use */ +#define CP_REC_TERM ( ( session->http ) ? CP_HTTP_REC_TERM : CP_SOCK_REC_TERM ) + + + +/*------------------------------------------------------------------------ + * Display a notification popup message to the user. + * + * @param type The type of notification: + * - info: PURPLE_NOTIFY_MSG_INFO + * - warning: PURPLE_NOTIFY_MSG_WARNING + * - error: PURPLE_NOTIFY_MSG_ERROR + * @param heading Heading text + * @param message Message text + */ +void mxit_popup( int type, const char* heading, const char* message ) +{ + /* (reference: "libpurple/notify.h") */ + purple_notify_message( NULL, type, _( MXIT_POPUP_WIN_NAME ), heading, message, NULL, NULL ); +} + + +/*------------------------------------------------------------------------ + * For compatibility with legacy clients, all usernames are sent from MXit with a domain + * appended. For MXit contacts, this domain is set to "@m". This function strips + * those fake domains. + * + * @param username The username of the contact + */ +void mxit_strip_domain( char* username ) +{ + if ( g_str_has_suffix( username, "@m" ) ) + username[ strlen(username) - 2 ] = '\0'; +} + + +/*------------------------------------------------------------------------ + * Dump a byte buffer to the console for debugging purposes. + * + * @param buf The data + * @param len The data length + */ +void dump_bytes( struct MXitSession* session, const char* buf, int len ) +{ + char msg[( len * 3 ) + 1]; + int i; + + memset( msg, 0x00, sizeof( msg ) ); + + for ( i = 0; i < len; i++ ) { + if ( buf[i] == CP_REC_TERM ) /* record terminator */ + msg[i] = '!'; + else if ( buf[i] == CP_FLD_TERM ) /* field terminator */ + msg[i] = '^'; + else if ( buf[i] == CP_PKT_TERM ) /* packet terminator */ + msg[i] = '@'; + else if ( buf[i] < 0x20 ) + msg[i] = '_'; + else + msg[i] = buf[i]; + + } + + purple_debug_info( MXIT_PLUGIN_ID, "DUMP: '%s'\n", msg ); +} + + +/*------------------------------------------------------------------------ + * Determine if we have an active chat with a specific contact + * + * @param session The MXit session object + * @param who The contact name + * @return Return true if we have an active chat with the contact + */ +gboolean find_active_chat( const GList* chats, const char* who ) +{ + const GList* list = chats; + const char* chat = NULL; + + while ( list ) { + chat = (const char*) list->data; + + if ( strcmp( chat, who ) == 0 ) + return TRUE; + + list = g_list_next( list ); + } + + return FALSE; +} + + +/*======================================================================================================================== + * Low-level Packet transmission + */ + +/*------------------------------------------------------------------------ + * Remove next packet from transmission queue. + * + * @param session The MXit session object + * @return The next packet for transmission (or NULL) + */ +static struct tx_packet* pop_tx_packet( struct MXitSession* session ) +{ + struct tx_packet* packet = NULL; + + if ( session->queue.count > 0 ) { + /* dequeue the next packet */ + packet = session->queue.packets[session->queue.rd_i]; + session->queue.packets[session->queue.rd_i] = NULL; + session->queue.rd_i = ( session->queue.rd_i + 1 ) % MAX_QUEUE_SIZE; + session->queue.count--; + } + + return packet; +} + + +/*------------------------------------------------------------------------ + * Add packet to transmission queue. + * + * @param session The MXit session object + * @param packet The packet to transmit + * @return Return TRUE if packet was enqueue, or FALSE if queue is full. + */ +static gboolean push_tx_packet( struct MXitSession* session, struct tx_packet* packet ) +{ + if ( session->queue.count < MAX_QUEUE_SIZE ) { + /* enqueue packet */ + session->queue.packets[session->queue.wr_i] = packet; + session->queue.wr_i = ( session->queue.wr_i + 1 ) % MAX_QUEUE_SIZE; + session->queue.count++; + return TRUE; + } + else + return FALSE; /* queue is full */ +} + + +/*------------------------------------------------------------------------ + * Deallocate transmission packet. + * + * @param packet The packet to deallocate. + */ +static void free_tx_packet( struct tx_packet* packet ) +{ + g_free( packet->data ); + g_free( packet ); + packet = NULL; +} + + +/*------------------------------------------------------------------------ + * Flush all the packets from the tx queue and release the resources. + * + * @param session The MXit session object + */ +static void flush_queue( struct MXitSession* session ) +{ + struct tx_packet* packet; + + purple_debug_info( MXIT_PLUGIN_ID, "flushing the tx queue\n" ); + + while ( (packet = pop_tx_packet( session ) ) != NULL ) + free_tx_packet( packet ); +} + + +/*------------------------------------------------------------------------ + * TX Step 3: Write the packet data to the TCP connection. + * + * @param fd The file descriptor + * @param pktdata The packet data + * @param pktlen The length of the packet data + * @return Return -1 on error, otherwise 0 + */ +static int mxit_write_sock_packet( int fd, const char* pktdata, int pktlen ) +{ + int written; + int res; + + written = 0; + while ( written < pktlen ) { + res = write( fd, &pktdata[written], pktlen - written ); + if ( res <= 0 ) { + /* error on socket */ + if ( errno == EAGAIN ) + continue; + + purple_debug_error( MXIT_PLUGIN_ID, "Error while writing packet to MXit server (%i)\n", res ); + return -1; + } + written += res; + } + + return 0; +} + + +/*------------------------------------------------------------------------ + * Callback called for handling a HTTP GET response + * + * @param url_data libPurple internal object (see purple_util_fetch_url_request) + * @param user_data The MXit session object + * @param url_text The data returned (could be NULL if error) + * @param len The length of the data returned (0 if error) + * @param error_message Descriptive error message + */ +static void mxit_cb_http_rx( PurpleUtilFetchUrlData* url_data, gpointer user_data, const gchar* url_text, gsize len, const gchar* error_message ) +{ + struct MXitSession* session = (struct MXitSession*) user_data; + + /* clear outstanding request */ + session->http_out_req = NULL; + + if ( ( !url_text ) || ( len == 0 ) ) { + /* error with request */ + purple_debug_error( MXIT_PLUGIN_ID, "HTTP response error (%s)\n", error_message ); + return; + } + + /* convert the HTTP result */ + memcpy( session->rx_dbuf, url_text, len ); + session->rx_i = len; + + mxit_parse_packet( session ); +} + + +/*------------------------------------------------------------------------ + * TX Step 3: Write the packet data to the HTTP connection (GET style). + * + * @param session The MXit session object + * @param pktdata The packet data + * @param pktlen The length of the packet data + * @return Return -1 on error, otherwise 0 + */ +static void mxit_write_http_get( struct MXitSession* session, struct tx_packet* packet ) +{ + char* part = NULL; + char* url = NULL; + + if ( packet->datalen > 0 ) { + char* tmp = NULL; + + tmp = g_strndup( packet->data, packet->datalen ); + part = g_strdup( purple_url_encode( tmp ) ); + g_free( tmp ); + } + + url = g_strdup_printf( "%s?%s%s", session->http_server, purple_url_encode( packet->header ), ( !part ) ? "" : part ); + +#ifdef DEBUG_PROTOCOL + purple_debug_info( MXIT_PLUGIN_ID, "HTTP GET: '%s'\n", url ); +#endif + + /* send the HTTP request */ + session->http_out_req = purple_util_fetch_url_request( url, TRUE, MXIT_HTTP_USERAGENT, TRUE, NULL, FALSE, mxit_cb_http_rx, session ); + + g_free( url ); + if ( part ) + g_free( part ); +} + + +/*------------------------------------------------------------------------ + * TX Step 3: Write the packet data to the HTTP connection (POST style). + * + * @param session The MXit session object + * @param pktdata The packet data + * @param pktlen The length of the packet data + * @return Return -1 on error, otherwise 0 + */ +static void mxit_write_http_post( struct MXitSession* session, struct tx_packet* packet ) +{ + char request[256 + packet->datalen]; + int reqlen; + char* host_name; + int host_port; + gboolean ok; + + /* extract the HTTP host name and host port number to connect to */ + ok = purple_url_parse( session->http_server, &host_name, &host_port, NULL, NULL, NULL ); + if ( !ok ) { + purple_debug_error( MXIT_PLUGIN_ID, "HTTP POST error: (host name '%s' not valid)\n", session->http_server ); + } + + /* strip off the last '&' from the header */ + packet->header[packet->headerlen - 1] = '\0'; + packet->headerlen--; + + /* build the HTTP request packet */ + reqlen = g_snprintf( request, 256, + "POST %s?%s HTTP/1.1\r\n" + "User-Agent: " MXIT_HTTP_USERAGENT "\r\n" + "Content-Type: application/octet-stream\r\n" + "Host: %s\r\n" + "Content-Length: %" G_GSIZE_FORMAT "\r\n" + "\r\n", + session->http_server, + purple_url_encode( packet->header ), + host_name, + packet->datalen - MXIT_MS_OFFSET + ); + + /* copy over the packet body data (could be binary) */ + memcpy( request + reqlen, packet->data + MXIT_MS_OFFSET, packet->datalen - MXIT_MS_OFFSET ); + reqlen += packet->datalen; + +#ifdef DEBUG_PROTOCOL + purple_debug_info( MXIT_PLUGIN_ID, "HTTP POST:\n" ); + dump_bytes( session, request, reqlen ); +#endif + + /* send the request to the HTTP server */ + mxit_http_send_request( session, host_name, host_port, request, reqlen ); +} + + +/*------------------------------------------------------------------------ + * TX Step 2: Handle the transmission of the packet to the MXit server. + * + * @param session The MXit session object + * @param packet The packet to transmit + */ +static void mxit_send_packet( struct MXitSession* session, struct tx_packet* packet ) +{ + int res; + + if ( !( session->flags & MXIT_FLAG_CONNECTED ) ) { + /* we are not connected so ignore all packets to be send */ + purple_debug_error( MXIT_PLUGIN_ID, "Dropping TX packet (we are not connected)\n" ); + return; + } + + purple_debug_info( MXIT_PLUGIN_ID, "Packet send CMD:%i (%i)\n", packet->cmd, packet->headerlen + packet->datalen ); +#ifdef DEBUG_PROTOCOL + dump_bytes( session, packet->header, packet->headerlen ); + dump_bytes( session, packet->data, packet->datalen ); +#endif + + if ( !session->http ) { + /* socket connection */ + char data[packet->datalen + packet->headerlen]; + int datalen; + + /* create raw data buffer */ + memcpy( data, packet->header, packet->headerlen ); + memcpy( data + packet->headerlen, packet->data, packet->datalen ); + datalen = packet->headerlen + packet->datalen; + + res = mxit_write_sock_packet( session->fd, data, datalen ); + if ( res < 0 ) { + /* we must have lost the connection, so terminate it so that we can reconnect */ + purple_connection_error( session->con, _( "We have lost the connection to MXit. Please reconnect." ) ); + } + } + else { + /* http connection */ + + if ( packet->cmd == CP_CMD_MEDIA ) { + /* multimedia packets must be send with a HTTP POST */ + mxit_write_http_post( session, packet ); + } + else { + mxit_write_http_get( session, packet ); + } + } + + /* update the timestamp of the last-transmitted packet */ + session->last_tx = time( NULL ); + + /* + * we need to remember that we are still waiting for the ACK from + * the server on this request + */ + session->outack = packet->cmd; + + /* free up the packet resources */ + free_tx_packet( packet ); +} + + +/*------------------------------------------------------------------------ + * TX Step 1: Create a new Tx packet and queue it for sending. + * + * @param session The MXit session object + * @param data The packet data (payload) + * @param datalen The length of the packet data + * @param cmd The MXit command for this packet + */ +static void mxit_queue_packet( struct MXitSession* session, const char* data, int datalen, int cmd ) +{ + struct tx_packet* packet; + char header[256]; + int hlen; + + /* create a packet for sending */ + packet = g_new0( struct tx_packet, 1 ); + packet->data = g_malloc0( datalen ); + packet->cmd = cmd; + packet->headerlen = 0; + + /* create generic packet header */ + hlen = sprintf( header, "id=%s%c", session->acc->username, CP_REC_TERM ); /* client msisdn */ + + if ( session->http ) { + /* http connection only */ + hlen += sprintf( header + hlen, "s=" ); + if ( session->http_sesid > 0 ) { + hlen += sprintf( header + hlen, "%u%c", session->http_sesid, CP_FLD_TERM ); /* http session id */ + } + session->http_seqno++; + hlen += sprintf( header + hlen, "%u%c", session->http_seqno, CP_REC_TERM ); /* http request sequence id */ + } + + hlen += sprintf( header + hlen, "cm=%i%c", cmd, CP_REC_TERM ); /* packet command */ + + if ( !session->http ) { + /* socket connection only */ + packet->headerlen += sprintf( packet->header, "ln=%i%c", ( datalen + hlen ), CP_REC_TERM ); /* packet length */ + } + + /* copy the header to packet */ + memcpy( packet->header + packet->headerlen, header, hlen ); + packet->headerlen += hlen; + + /* copy payload to packet */ + if ( datalen > 0 ) + memcpy( packet->data, data, datalen ); + packet->datalen = datalen; + + + /* + * shortcut: first check if there are any commands still outstanding. + * if not, then we might as well just write this packet directly and + * skip the whole queueing thing + */ + if ( session->outack == 0 ) { + /* no outstanding ACKs, so we might as well write it directly */ + mxit_send_packet( session, packet ); + } + else { + /* ACK still outstanding, so we need to queue this request until we have the ACK */ + + if ( ( packet->cmd == CP_CMD_PING ) || ( packet->cmd == CP_CMD_POLL ) ) { + /* we do NOT queue HTTP poll nor socket ping packets */ + free_tx_packet( packet ); + return; + } + + purple_debug_info( MXIT_PLUGIN_ID, "queueing packet for later sending cmd=%i\n", cmd ); + if ( !push_tx_packet( session, packet ) ) { + /* packet could not be queued for transmission */ + mxit_popup( PURPLE_NOTIFY_MSG_ERROR, _( "Message Send Error" ), _( "Unable to process your request at this time" ) ); + free_tx_packet( packet ); + } + } +} + + +/*------------------------------------------------------------------------ + * Callback to manage the packet send queue (send next packet, timeout's, etc). + * + * @param session The MXit session object + */ +gboolean mxit_manage_queue( gpointer user_data ) +{ + struct MXitSession* session = (struct MXitSession*) user_data; + struct tx_packet* packet = NULL; + + if ( !( session->flags & MXIT_FLAG_CONNECTED ) ) { + /* we are not connected, so ignore the queue */ + return TRUE; + } + else if ( session->outack > 0 ) { + /* we are still waiting for an outstanding ACK from the MXit server */ + if ( session->last_tx <= time( NULL ) - MXIT_ACK_TIMEOUT ) { + /* ack timeout! so we close the connection here */ + purple_debug_info( MXIT_PLUGIN_ID, "mxit_manage_queue: Timeout awaiting ACK for command '%X'\n", session->outack ); + purple_connection_error( session->con, _( "Timeout while waiting for a response from the MXit server." ) ); + } + return TRUE; + } + + packet = pop_tx_packet( session ); + if ( packet != NULL ) { + /* there was a packet waiting to be sent to the server, now is the time to do something about it */ + + /* send the packet to MXit server */ + mxit_send_packet( session, packet ); + } + + return TRUE; +} + + +/*------------------------------------------------------------------------ + * Callback to manage HTTP server polling (HTTP connections ONLY) + * + * @param session The MXit session object + */ +gboolean mxit_manage_polling( gpointer user_data ) +{ + struct MXitSession* session = (struct MXitSession*) user_data; + gboolean poll = FALSE; + time_t now = time( NULL ); + int polldiff; + int rxdiff; + + if ( !( session->flags & MXIT_FLAG_LOGGEDIN ) ) { + /* we only poll if we are actually logged in */ + return TRUE; + } + + /* calculate the time differences */ + rxdiff = now - session->last_rx; + polldiff = now - session->http_last_poll; + + if ( rxdiff < MXIT_HTTP_POLL_MIN ) { + /* we received some reply a few moments ago, so reset the poll interval */ + session->http_interval = MXIT_HTTP_POLL_MIN; + } + else if ( session->http_last_poll < ( now - session->http_interval ) ) { + /* time to poll again */ + poll = TRUE; + + /* back-off some more with the polling */ + session->http_interval = session->http_interval + ( session->http_interval / 2 ); + if ( session->http_interval > MXIT_HTTP_POLL_MAX ) + session->http_interval = MXIT_HTTP_POLL_MAX; + } + + /* debugging */ + //purple_debug_info( MXIT_PLUGIN_ID, "POLL TIMER: %i (%i,%i)\n", session->http_interval, rxdiff, polldiff ); + + if ( poll ) { + /* send poll request */ + session->http_last_poll = time( NULL ); + mxit_send_poll( session ); + } + + return TRUE; +} + + +/*======================================================================================================================== + * Send MXit operations. + */ + +/*------------------------------------------------------------------------ + * Send a ping/keepalive packet to MXit server. + * + * @param session The MXit session object + */ +void mxit_send_ping( struct MXitSession* session ) +{ + /* queue packet for transmission */ + mxit_queue_packet( session, NULL, 0, CP_CMD_PING ); +} + + +/*------------------------------------------------------------------------ + * Send a poll request to the HTTP server (HTTP connections ONLY). + * + * @param session The MXit session object + */ +void mxit_send_poll( struct MXitSession* session ) +{ + /* queue packet for transmission */ + mxit_queue_packet( session, NULL, 0, CP_CMD_POLL ); +} + + +/*------------------------------------------------------------------------ + * Send a logout packet to the MXit server. + * + * @param session The MXit session object + */ +void mxit_send_logout( struct MXitSession* session ) +{ + /* queue packet for transmission */ + mxit_queue_packet( session, NULL, 0, CP_CMD_LOGOUT ); +} + + +/*------------------------------------------------------------------------ + * Send a register packet to the MXit server. + * + * @param session The MXit session object + */ +void mxit_send_register( struct MXitSession* session ) +{ + struct MXitProfile* profile = session->profile; + const char* locale; + char data[CP_MAX_PACKET]; + int datalen; + + locale = purple_account_get_string( session->acc, MXIT_CONFIG_LOCALE, MXIT_DEFAULT_LOCALE ); + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=%s%c%s%c%i%c%s%c" /* "ms"=password\1version\1maxreplyLen\1name\1 */ + "%s%c%i%c%s%c%s%c" /* dateOfBirth\1gender\1location\1capabilities\1 */ + "%s%c%i%c%s%c%s", /* dc\1features\1dialingcode\1locale */ + session->encpwd, CP_FLD_TERM, MXIT_CP_VERSION, CP_FLD_TERM, CP_MAX_FILESIZE, CP_FLD_TERM, profile->nickname, CP_FLD_TERM, + profile->birthday, CP_FLD_TERM, ( profile->male ) ? 1 : 0, CP_FLD_TERM, MXIT_DEFAULT_LOC, CP_FLD_TERM, MXIT_CP_CAP, CP_FLD_TERM, + session->distcode, CP_FLD_TERM, MXIT_CP_FEATURES, CP_FLD_TERM, session->dialcode, CP_FLD_TERM, locale + ); + + /* queue packet for transmission */ + mxit_queue_packet( session, data, datalen, CP_CMD_REGISTER ); +} + + +/*------------------------------------------------------------------------ + * Send a login packet to the MXit server. + * + * @param session The MXit session object + */ +void mxit_send_login( struct MXitSession* session ) +{ + const char* splashId; + const char* locale; + char data[CP_MAX_PACKET]; + int datalen; + + locale = purple_account_get_string( session->acc, MXIT_CONFIG_LOCALE, MXIT_DEFAULT_LOCALE ); + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=%s%c%s%c%i%c" /* "ms"=password\1version\1getContacts\1 */ + "%s%c%s%c%i%c" /* capabilities\1dc\1features\1 */ + "%s%c%s", /* dialingcode\1locale */ + session->encpwd, CP_FLD_TERM, MXIT_CP_VERSION, CP_FLD_TERM, 1, CP_FLD_TERM, + MXIT_CP_CAP, CP_FLD_TERM, session->distcode, CP_FLD_TERM, MXIT_CP_FEATURES, CP_FLD_TERM, + session->dialcode, CP_FLD_TERM, locale + ); + + /* include "custom resource" information */ + splashId = splash_current( session ); + if ( splashId != NULL ) + datalen += sprintf( data + datalen, "%ccr=%s", CP_REC_TERM, splashId ); + + /* queue packet for transmission */ + mxit_queue_packet( session, data, datalen, CP_CMD_LOGIN ); +} + + +/*------------------------------------------------------------------------ + * Send a chat message packet to the MXit server. + * + * @param session The MXit session object + * @param to The username of the recipient + * @param msg The message text + */ +void mxit_send_message( struct MXitSession* session, const char* to, const char* msg, gboolean parse_markup ) +{ + char data[CP_MAX_PACKET]; + char* markuped_msg; + int datalen; + int msgtype = CP_MSGTYPE_NORMAL; + + /* first we need to convert the markup from libPurple to MXit format */ + if ( parse_markup ) + markuped_msg = mxit_convert_markup_tx( msg, &msgtype ); + else + markuped_msg = g_strdup( msg ); + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=%s%c%s%c%i%c%i", /* "ms"=jid\1msg\1type\1flags */ + to, CP_FLD_TERM, markuped_msg, CP_FLD_TERM, msgtype, CP_FLD_TERM, CP_MSG_MARKUP | CP_MSG_EMOTICON + ); + + /* free the resources */ + g_free( markuped_msg ); + + /* queue packet for transmission */ + mxit_queue_packet( session, data, datalen, CP_CMD_TX_MSG ); +} + + +/*------------------------------------------------------------------------ + * Send a extended profile request packet to the MXit server. + * + * @param session The MXit session object + * @param username Username who's profile is being requested (NULL = our own) + * @param nr_attribs Number of attributes being requested + * @param attributes The names of the attributes + */ +void mxit_send_extprofile_request( struct MXitSession* session, const char* username, unsigned int nr_attrib, const char* attribute[] ) +{ + char data[CP_MAX_PACKET]; + int datalen; + unsigned int i; + + datalen = sprintf( data, "ms=%s%c%i", /* "ms="mxitid\1nr_attributes */ + (username ? username : ""), CP_FLD_TERM, nr_attrib); + + /* add attributes */ + for ( i = 0; i < nr_attrib; i++ ) + datalen += sprintf( data + datalen, "%c%s", CP_FLD_TERM, attribute[i] ); + + /* queue packet for transmission */ + mxit_queue_packet( session, data, datalen, CP_CMD_EXTPROFILE_GET ); +} + + +/*------------------------------------------------------------------------ + * Send an update profile packet to the MXit server. + * + * @param session The MXit session object + * @param password The new password to be used for logging in (optional) + * @param nr_attrib The number of attributes + * @param attributes String containing the attributes and settings seperated by '0x01' + */ +void mxit_send_extprofile_update( struct MXitSession* session, const char* password, unsigned int nr_attrib, const char* attributes ) +{ + char data[CP_MAX_PACKET]; + gchar** parts; + int datalen; + unsigned int i; + + parts = g_strsplit( attributes, "\01", ( MXIT_MAX_ATTRIBS * 3 ) ); + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=%s%c%i", /* "ms"=password\1nr_attibutes */ + ( password ) ? password : "", CP_FLD_TERM, nr_attrib + ); + + /* add attributes */ + for ( i = 1; i < nr_attrib * 3; i+=3 ) + datalen += sprintf( data + datalen, "%c%s%c%s%c%s", /* \1name\1type\1value */ + CP_FLD_TERM, parts[i], CP_FLD_TERM, parts[i + 1], CP_FLD_TERM, parts[i + 2] ); + + /* queue packet for transmission */ + mxit_queue_packet( session, data, datalen, CP_CMD_EXTPROFILE_SET ); + + /* freeup the memory */ + g_strfreev( parts ); +} + + +/*------------------------------------------------------------------------ + * Send a presence update packet to the MXit server. + * + * @param session The MXit session object + * @param presence The presence (as per MXit types) + * @param statusmsg The status message (can be NULL) + */ +void mxit_send_presence( struct MXitSession* session, int presence, const char* statusmsg ) +{ + char data[CP_MAX_PACKET]; + int datalen; + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=%i%c", /* "ms"=show\1status */ + presence, CP_FLD_TERM + ); + + /* append status message (if one is set) */ + if ( statusmsg ) + datalen += sprintf( data + datalen, "%s", statusmsg ); + + /* queue packet for transmission */ + mxit_queue_packet( session, data, datalen, CP_CMD_STATUS ); +} + + +/*------------------------------------------------------------------------ + * Send a mood update packet to the MXit server. + * + * @param session The MXit session object + * @param mood The mood (as per MXit types) + */ +void mxit_send_mood( struct MXitSession* session, int mood ) +{ + char data[CP_MAX_PACKET]; + int datalen; + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=%i", /* "ms"=mood */ + mood + ); + + /* queue packet for transmission */ + mxit_queue_packet( session, data, datalen, CP_CMD_MOOD ); +} + + +/*------------------------------------------------------------------------ + * Send an invite contact packet to the MXit server. + * + * @param session The MXit session object + * @param username The username of the contact being invited + * @param alias Our alias for the contact + * @param groupname Group in which contact should be stored. + */ +void mxit_send_invite( struct MXitSession* session, const char* username, const char* alias, const char* groupname ) +{ + char data[CP_MAX_PACKET]; + int datalen; + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=%s%c%s%c%s%c%i%c%s", /* "ms"=group\1username\1alias\1type\1msg */ + groupname, CP_FLD_TERM, username, CP_FLD_TERM, alias, + CP_FLD_TERM, MXIT_TYPE_MXIT, CP_FLD_TERM, "" + ); + + /* queue packet for transmission */ + mxit_queue_packet( session, data, datalen, CP_CMD_INVITE ); +} + + +/*------------------------------------------------------------------------ + * Send a remove contact packet to the MXit server. + * + * @param session The MXit session object + * @param username The username of the contact being removed + */ +void mxit_send_remove( struct MXitSession* session, const char* username ) +{ + char data[CP_MAX_PACKET]; + int datalen; + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=%s", /* "ms"=username */ + username + ); + + /* queue packet for transmission */ + mxit_queue_packet( session, data, datalen, CP_CMD_REMOVE ); +} + + +/*------------------------------------------------------------------------ + * Send an accept subscription (invite) packet to the MXit server. + * + * @param session The MXit session object + * @param username The username of the contact being accepted + * @param alias Our alias for the contact + */ +void mxit_send_allow_sub( struct MXitSession* session, const char* username, const char* alias ) +{ + char data[CP_MAX_PACKET]; + int datalen; + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=%s%c%s%c%s", /* "ms"=username\1group\1alias */ + username, CP_FLD_TERM, "", CP_FLD_TERM, alias + ); + + /* queue packet for transmission */ + mxit_queue_packet( session, data, datalen, CP_CMD_ALLOW ); +} + + +/*------------------------------------------------------------------------ + * Send an deny subscription (invite) packet to the MXit server. + * + * @param session The MXit session object + * @param username The username of the contact being denied + */ +void mxit_send_deny_sub( struct MXitSession* session, const char* username ) +{ + char data[CP_MAX_PACKET]; + int datalen; + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=%s", /* "ms"=username */ + username + ); + + /* queue packet for transmission */ + mxit_queue_packet( session, data, datalen, CP_CMD_DENY ); +} + + +/*------------------------------------------------------------------------ + * Send an update contact packet to the MXit server. + * + * @param session The MXit session object + * @param username The username of the contact being denied + * @param alias Our alias for the contact + * @param groupname Group in which contact should be stored. + */ +void mxit_send_update_contact( struct MXitSession* session, const char* username, const char* alias, const char* groupname ) +{ + char data[CP_MAX_PACKET]; + int datalen; + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=%s%c%s%c%s", /* "ms"=groupname\1username\1alias */ + groupname, CP_FLD_TERM, username, CP_FLD_TERM, alias + ); + + /* queue packet for transmission */ + mxit_queue_packet( session, data, datalen, CP_CMD_UPDATE ); +} + + +/*------------------------------------------------------------------------ + * Send a splash-screen click event packet. + * + * @param session The MXit session object + * @param splashid The identifier of the splash-screen + */ +void mxit_send_splashclick( struct MXitSession* session, const char* splashid ) +{ + char data[CP_MAX_PACKET]; + int datalen; + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=%s", /* "ms"=splashId */ + splashid + ); + + /* queue packet for transmission */ + mxit_queue_packet( session, data, datalen, CP_CMD_SPLASHCLICK ); +} + + +/*------------------------------------------------------------------------ + * Send packet to create a MultiMX room. + * + * @param session The MXit session object + * @param groupname Name of the room to create + * @param nr_usernames Number of users in initial invite + * @param usernames The usernames of the users in the initial invite + */ +void mxit_send_groupchat_create( struct MXitSession* session, const char* groupname, int nr_usernames, const char* usernames[] ) +{ + char data[CP_MAX_PACKET]; + int datalen; + int i; + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=%s%c%i", /* "ms"=roomname\1nr_jids\1jid0\1..\1jidN */ + groupname, CP_FLD_TERM, nr_usernames + ); + + /* add usernames */ + for ( i = 0; i < nr_usernames; i++ ) + datalen += sprintf( data + datalen, "%c%s", CP_FLD_TERM, usernames[i] ); + + /* queue packet for transmission */ + mxit_queue_packet( session, data, datalen, CP_CMD_GRPCHAT_CREATE ); +} + + +/*------------------------------------------------------------------------ + * Send packet to invite users to existing MultiMX room. + * + * @param session The MXit session object + * @param roomid The unique RoomID for the MultiMx room. + * @param nr_usernames Number of users being invited + * @param usernames The usernames of the users being invited + */ + +void mxit_send_groupchat_invite( struct MXitSession* session, const char* roomid, int nr_usernames, const char* usernames[] ) +{ + char data[CP_MAX_PACKET]; + int datalen; + int i; + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=%s%c%i", /* "ms"=roomid\1nr_jids\1jid0\1..\1jidN */ + roomid, CP_FLD_TERM, nr_usernames + ); + + /* add usernames */ + for ( i = 0; i < nr_usernames; i++ ) + datalen += sprintf( data + datalen, "%c%s", CP_FLD_TERM, usernames[i] ); + + /* queue packet for transmission */ + mxit_queue_packet( session, data, datalen, CP_CMD_GRPCHAT_INVITE ); +} + + +/*------------------------------------------------------------------------ + * Send a "send file direct" multimedia packet. + * + * @param session The MXit session object + * @param username The username of the recipient + * @param filename The name of the file being sent + * @param buf The content of the file + * @param buflen The length of the file contents + */ +void mxit_send_file( struct MXitSession* session, const char* username, const char* filename, const unsigned char* buf, int buflen ) +{ + char data[CP_MAX_PACKET]; + int datalen = 0; + struct raw_chunk* chunk; + int size; + + purple_debug_info( MXIT_PLUGIN_ID, "SENDING FILE '%s' of %i bytes to user '%s'\n", filename, buflen, username ); + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=" ); + + /* map chunk header over data buffer */ + chunk = (struct raw_chunk *) &data[datalen]; + + size = mxit_chunk_create_senddirect( chunk->data, username, filename, buf, buflen ); + if ( size < 0 ) { + purple_debug_error( MXIT_PLUGIN_ID, "Error creating senddirect chunk (%i)\n", size ); + return; + } + + chunk->type = CP_CHUNK_DIRECT_SND; + chunk->length = htonl( size ); + datalen += sizeof( struct raw_chunk ) + size; + + /* send the byte stream to the mxit server */ + mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA ); +} + + +/*------------------------------------------------------------------------ + * Send a "reject file" multimedia packet. + * + * @param session The MXit session object + * @param fileid A unique ID that identifies this file + */ +void mxit_send_file_reject( struct MXitSession* session, const char* fileid ) +{ + char data[CP_MAX_PACKET]; + int datalen = 0; + struct raw_chunk* chunk; + int size; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_send_file_reject\n" ); + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=" ); + + /* map chunk header over data buffer */ + chunk = (struct raw_chunk *) &data[datalen]; + + size = mxit_chunk_create_reject( chunk->data, fileid ); + if ( size < 0 ) { + purple_debug_error( MXIT_PLUGIN_ID, "Error creating reject chunk (%i)\n", size ); + return; + } + + chunk->type = CP_CHUNK_REJECT; + chunk->length = htonl( size ); + datalen += sizeof( struct raw_chunk ) + size; + + /* send the byte stream to the mxit server */ + mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA ); +} + + +/*------------------------------------------------------------------------ + * Send a "get file" multimedia packet. + * + * @param session The MXit session object + * @param fileid A unique ID that identifies this file + * @param filesize The number of bytes to retrieve + * @param offset Offset in file at which to start retrieving + */ +void mxit_send_file_accept( struct MXitSession* session, const char* fileid, int filesize, int offset ) +{ + char data[CP_MAX_PACKET]; + int datalen = 0; + struct raw_chunk* chunk; + int size; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_send_file_accept\n" ); + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=" ); + + /* map chunk header over data buffer */ + chunk = (struct raw_chunk *) &data[datalen]; + + size = mxit_chunk_create_get( chunk->data, fileid, filesize, offset ); + if ( size < 0 ) { + purple_debug_error( MXIT_PLUGIN_ID, "Error creating getfile chunk (%i)\n", size ); + return; + } + + chunk->type = CP_CHUNK_GET; + chunk->length = htonl( size ); + datalen += sizeof( struct raw_chunk ) + size; + + /* send the byte stream to the mxit server */ + mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA ); +} + + +/*------------------------------------------------------------------------ + * Send a "received file" multimedia packet. + * + * @param session The MXit session object + * @param status The status of the file-transfer + */ +void mxit_send_file_received( struct MXitSession* session, const char* fileid, short status ) +{ + char data[CP_MAX_PACKET]; + int datalen = 0; + struct raw_chunk* chunk; + int size; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_send_file_received\n" ); + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=" ); + + /* map chunk header over data buffer */ + chunk = (struct raw_chunk *) &data[datalen]; + + size = mxit_chunk_create_received( chunk->data, fileid, status ); + if ( size < 0 ) { + purple_debug_error( MXIT_PLUGIN_ID, "Error creating received chunk (%i)\n", size ); + return; + } + + chunk->type = CP_CHUNK_RECIEVED; + chunk->length = htonl( size ); + datalen += sizeof( struct raw_chunk ) + size; + + /* send the byte stream to the mxit server */ + mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA ); +} + + +/*------------------------------------------------------------------------ + * Send a "set avatar" multimedia packet. + * + * @param session The MXit session object + * @param data The avatar data + * @param buflen The length of the avatar data + */ +void mxit_set_avatar( struct MXitSession* session, const unsigned char* avatar, int avatarlen ) +{ + char data[CP_MAX_PACKET]; + int datalen = 0; + struct raw_chunk* chunk; + int size; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_set_avatar: %i bytes\n", avatarlen ); + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=" ); + + /* map chunk header over data buffer */ + chunk = (struct raw_chunk *) &data[datalen]; + + size = mxit_chunk_create_set_avatar( chunk->data, avatar, avatarlen ); + if ( size < 0 ) { + purple_debug_error( MXIT_PLUGIN_ID, "Error creating set avatar chunk (%i)\n", size ); + return; + } + + chunk->type = CP_CHUNK_SET_AVATAR; + chunk->length = htonl( size ); + datalen += sizeof( struct raw_chunk ) + size; + + /* send the byte stream to the mxit server */ + mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA ); +} + + +/*------------------------------------------------------------------------ + * Send a "get avatar" multimedia packet. + * + * @param session The MXit session object + * @param mxitId The username who's avatar to request + * @param avatarId The id of the avatar image (as string) + * @param data The avatar data + * @param buflen The length of the avatar data + */ +void mxit_get_avatar( struct MXitSession* session, const char* mxitId, const char* avatarId ) +{ + char data[CP_MAX_PACKET]; + int datalen = 0; + struct raw_chunk* chunk; + int size; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_get_avatar: %s\n", mxitId ); + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=" ); + + /* map chunk header over data buffer */ + chunk = (struct raw_chunk *) &data[datalen]; + + size = mxit_chunk_create_get_avatar( chunk->data, mxitId, avatarId, MXIT_AVATAR_SIZE ); + if ( size < 0 ) { + purple_debug_error( MXIT_PLUGIN_ID, "Error creating get avatar chunk (%i)\n", size ); + return; + } + + chunk->type = CP_CHUNK_GET_AVATAR; + chunk->length = htonl( size ); + datalen += sizeof( struct raw_chunk ) + size; + + /* send the byte stream to the mxit server */ + mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA ); +} + + +/*------------------------------------------------------------------------ + * Process a login message packet. + * + * @param session The MXit session object + * @param records The packet's data records + * @param rcount The number of data records + */ +static void mxit_parse_cmd_login( struct MXitSession* session, struct record** records, int rcount ) +{ + PurpleStatus* status; + int presence; + const char* profilelist[] = { CP_PROFILE_BIRTHDATE, CP_PROFILE_GENDER, CP_PROFILE_HIDENUMBER, CP_PROFILE_FULLNAME, + CP_PROFILE_TITLE, CP_PROFILE_FIRSTNAME, CP_PROFILE_LASTNAME, CP_PROFILE_EMAIL, + CP_PROFILE_MOBILENR }; + + purple_account_set_int( session->acc, MXIT_CONFIG_STATE, MXIT_STATE_LOGIN ); + + /* we were not yet logged in so we need to complete the login sequence here */ + session->flags |= MXIT_FLAG_LOGGEDIN; + purple_connection_update_progress( session->con, _( "Successfully Logged In..." ), 3, 4 ); + purple_connection_set_state( session->con, PURPLE_CONNECTED ); + + /* display the current splash-screen */ + if ( splash_popup_enabled( session ) ) + splash_display( session ); + + /* update presence status */ + status = purple_account_get_active_status( session->acc ); + presence = mxit_convert_presence( purple_status_get_id( status ) ); + if ( presence != MXIT_PRESENCE_ONLINE ) { + /* when logging into MXit, your default presence is online. but with the UI, one can change + * the presence to whatever. in the case where its changed to a different presence setting + * we need to send an update to the server, otherwise the user's presence will be out of + * sync between the UI and MXit. + */ + mxit_send_presence( session, presence, purple_status_get_attr_string( status, "message" ) ); + } + + /* save extra info if this is a HTTP connection */ + if ( session->http ) { + /* save the http server to use for this session */ + g_strlcpy( session->http_server, records[1]->fields[3]->data, sizeof( session->http_server ) ); + + /* save the session id */ + session->http_sesid = atoi( records[0]->fields[0]->data ); + } + + /* retrieve our MXit profile */ + mxit_send_extprofile_request( session, NULL, ARRAY_SIZE( profilelist ), profilelist ); +} + + +/*------------------------------------------------------------------------ + * Process a received message packet. + * + * @param session The MXit session object + * @param records The packet's data records + * @param rcount The number of data records + */ +static void mxit_parse_cmd_message( struct MXitSession* session, struct record** records, int rcount ) +{ + struct RXMsgData* mx = NULL; + char* message = NULL; + int msglen = 0; + int msgflags = 0; + int msgtype = 0; + + if ( ( rcount == 1 ) || ( records[0]->fcount < 2 ) || ( records[1]->fcount == 0 ) || ( records[1]->fields[0]->len == 0 ) ) { + /* packet contains no message or an empty message */ + return; + } + + message = records[1]->fields[0]->data; + msglen = strlen( message ); + + /* strip off dummy domain */ + mxit_strip_domain( records[0]->fields[0]->data ); + +#ifdef DEBUG_PROTOCOL + purple_debug_info( MXIT_PLUGIN_ID, "Message received from '%s'\n", records[0]->fields[0]->data ); +#endif + + /* decode message flags (if any) */ + if ( records[0]->fcount >= 5 ) + msgflags = atoi( records[0]->fields[4]->data ); + msgtype = atoi( records[0]->fields[2]->data ); + + if ( msgflags & CP_MSG_ENCRYPTED ) { + /* this is an encrypted message. we do not currently support those so ignore it */ + PurpleBuddy* buddy; + const char* name; + char msg[128]; + + buddy = purple_find_buddy( session->acc, records[0]->fields[0]->data ); + if ( buddy ) + name = purple_buddy_get_alias( buddy ); + else + name = records[0]->fields[0]->data; + g_snprintf( msg, sizeof( msg ), "%s sent you an encrypted message, but it is not supported on this client.", name ); + mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Message Error" ), _( msg ) ); + return; + } + + /* create and initialise new markup struct */ + mx = g_new0( struct RXMsgData, 1 ); + mx->msg = g_string_sized_new( msglen ); + mx->session = session; + mx->from = g_strdup( records[0]->fields[0]->data ); + mx->timestamp = atoi( records[0]->fields[1]->data ); + mx->got_img = FALSE; + mx->chatid = -1; + mx->img_count = 0; + + /* update list of active chats */ + if ( !find_active_chat( session->active_chats, mx->from ) ) { + session->active_chats = g_list_append( session->active_chats, g_strdup( mx->from ) ); + } + + if ( is_multimx_contact( session, mx->from ) ) { + /* this is a MultiMx chatroom message */ + multimx_message_received( mx, message, msglen, msgtype, msgflags ); + } + else { + mxit_parse_markup( mx, message, msglen, msgtype, msgflags ); + } + + /* we are now done parsing the message */ + mx->converted = TRUE; + if ( mx->img_count == 0 ) { + /* we have all the data we need for this message to be displayed now. */ + mxit_show_message( mx ); + } + else { + /* this means there are still images outstanding for this message and + * still need to wait for them before we can display the message. + * so the image received callback function will eventually display + * the message. */ + } +} + + +/*------------------------------------------------------------------------ + * Process a received subscription request packet. + * + * @param session The MXit session object + * @param records The packet's data records + * @param rcount The number of data records + */ +static void mxit_parse_cmd_new_sub( struct MXitSession* session, struct record** records, int rcount ) +{ + struct contact* contact; + struct record* rec; + int i; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_new_sub (%i recs)\n", rcount ); + + for ( i = 0; i < rcount; i++ ) { + rec = records[i]; + + if ( rec->fcount < 4 ) { + purple_debug_error( MXIT_PLUGIN_ID, "BAD SUBSCRIPTION RECORD! %i fields\n", rec->fcount ); + break; + } + + /* build up a new contact info struct */ + contact = g_new0( struct contact, 1 ); + + strcpy( contact->username, rec->fields[0]->data ); + mxit_strip_domain( contact->username ); /* remove dummy domain */ + strcpy( contact->alias, rec->fields[1]->data ); + contact->type = atoi( rec->fields[2]->data ); + + if ( rec->fcount >= 5 ) { + /* there is a personal invite message attached */ + contact->msg = strdup( rec->fields[4]->data ); + } + else + contact->msg = NULL; + + /* handle the subscription */ + if ( contact-> type == MXIT_TYPE_MULTIMX ) { /* subscription to a MultiMX room */ + char* creator = NULL; + + if ( rec->fcount >= 6 ) + creator = rec->fields[5]->data; + + multimx_invite( session, contact, creator ); + } + else + mxit_new_subscription( session, contact ); + } +} + + +/*------------------------------------------------------------------------ + * Process a received contact update packet. + * + * @param session The MXit session object + * @param records The packet's data records + * @param rcount The number of data records + */ +static void mxit_parse_cmd_contact( struct MXitSession* session, struct record** records, int rcount ) +{ + struct contact* contact = NULL; + struct record* rec; + int i; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_contact (%i recs)\n", rcount ); + + for ( i = 0; i < rcount; i++ ) { + rec = records[i]; + + if ( rec->fcount < 6 ) { + purple_debug_error( MXIT_PLUGIN_ID, "BAD CONTACT RECORD! %i fields\n", rec->fcount ); + break; + } + + /* build up a new contact info struct */ + contact = g_new0( struct contact, 1 ); + + strcpy( contact->groupname, rec->fields[0]->data ); + strcpy( contact->username, rec->fields[1]->data ); + mxit_strip_domain( contact->username ); /* remove dummy domain */ + strcpy( contact->alias, rec->fields[2]->data ); + + contact->presence = atoi( rec->fields[3]->data ); + contact->type = atoi( rec->fields[4]->data ); + contact->mood = atoi( rec->fields[5]->data ); + + if ( rec->fcount > 6 ) { + /* added in protocol 5.9.0 - flags & subtype */ + contact->flags = atoi( rec->fields[6]->data ); + contact->subtype = rec->fields[7]->data[0]; + } + + /* add the contact to the buddy list */ + if ( contact-> type == MXIT_TYPE_MULTIMX ) /* contact is a MultiMX room */ + multimx_created( session, contact ); + else + mxit_update_contact( session, contact ); + } + + if ( !( session->flags & MXIT_FLAG_FIRSTROSTER ) ) { + session->flags |= MXIT_FLAG_FIRSTROSTER; + mxit_update_blist( session ); + } +} + + +/*------------------------------------------------------------------------ + * Process a received presence update packet. + * + * @param session The MXit session object + * @param records The packet's data records + * @param rcount The number of data records + */ +static void mxit_parse_cmd_presence( struct MXitSession* session, struct record** records, int rcount ) +{ + struct record* rec; + int i; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_presence (%i recs)\n", rcount ); + + for ( i = 0; i < rcount; i++ ) { + rec = records[i]; + + if ( rec->fcount < 6 ) { + purple_debug_error( MXIT_PLUGIN_ID, "BAD PRESENCE RECORD! %i fields\n", rec->fcount ); + break; + } + + /* + * The format of the record is: + * contactAddressN\1presenceN\1\moodN\1customMoodN\1statusMsgN\1avatarIdN + */ + mxit_strip_domain( rec->fields[0]->data ); /* contactAddress */ + + mxit_update_buddy_presence( session, rec->fields[0]->data, atoi( rec->fields[1]->data ), atoi( rec->fields[2]->data ), + rec->fields[3]->data, rec->fields[4]->data, rec->fields[5]->data ); + } +} + + +/*------------------------------------------------------------------------ + * Process a received extended profile packet. + * + * @param session The MXit session object + * @param records The packet's data records + * @param rcount The number of data records + */ +static void mxit_parse_cmd_extprofile( struct MXitSession* session, struct record** records, int rcount ) +{ + const char* mxitId = records[0]->fields[0]->data; + struct MXitProfile* profile = NULL; + int count; + int i; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_extprofile: profile for '%s'\n", mxitId ); + + profile = g_new0( struct MXitProfile, 1 ); + + /* set the count for attributes */ + count = atoi( records[0]->fields[1]->data ); + + for ( i = 0; i < count; i++ ) { + char* fname; + char* fvalue; + char* fstatus; + int f = ( i * 3 ) + 2; + + fname = records[0]->fields[f]->data; /* field name */ + fvalue = records[0]->fields[f + 1]->data; /* field value */ + fstatus = records[0]->fields[f + 2]->data; /* field status */ + + /* first check the status on the returned attribute */ + if ( fstatus[0] != '0' ) { + /* error: attribute requested was NOT found */ + purple_debug_error( MXIT_PLUGIN_ID, "Bad profile status on attribute '%s' \n", fname ); + continue; + } + + if ( strcmp( CP_PROFILE_BIRTHDATE, fname ) == 0 ) { + /* birthdate */ + if ( records[0]->fields[f + 1]->len > 10 ) { + fvalue[10] = '\0'; + records[0]->fields[f + 1]->len = 10; + } + memcpy( profile->birthday, fvalue, records[0]->fields[f + 1]->len ); + } + else if ( strcmp( CP_PROFILE_GENDER, fname ) == 0 ) { + /* gender */ + profile->male = ( fvalue[0] == '1' ); + } + else if ( strcmp( CP_PROFILE_HIDENUMBER, fname ) == 0 ) { + /* hide number */ + profile->hidden = ( fvalue[0] == '1' ); + } + else if ( strcmp( CP_PROFILE_FULLNAME, fname ) == 0 ) { + /* nickname */ + g_strlcpy( profile->nickname, fvalue, sizeof( profile->nickname ) ); + } + else if ( strcmp( CP_PROFILE_AVATAR, fname ) == 0 ) { + /* avatar id, we just ingore it cause we dont need it */ + } + else if ( strcmp( CP_PROFILE_TITLE, fname ) == 0 ) { + /* title */ + g_strlcpy( profile->title, fvalue, sizeof( profile->title ) ); + } + else if ( strcmp( CP_PROFILE_FIRSTNAME, fname ) == 0 ) { + /* first name */ + g_strlcpy( profile->firstname, fvalue, sizeof( profile->firstname ) ); + } + else if ( strcmp( CP_PROFILE_LASTNAME, fname ) == 0 ) { + /* last name */ + g_strlcpy( profile->lastname, fvalue, sizeof( profile->lastname ) ); + } + else if ( strcmp( CP_PROFILE_EMAIL, fname ) == 0 ) { + /* email address */ + g_strlcpy( profile->email, fvalue, sizeof( profile->email ) ); + } + else if ( strcmp( CP_PROFILE_MOBILENR, fname ) == 0 ) { + /* mobile number */ + g_strlcpy( profile->mobilenr, fvalue, sizeof( profile->mobilenr ) ); + } + else { + /* invalid profile attribute */ + purple_debug_error( MXIT_PLUGIN_ID, "Invalid profile attribute received '%s' \n", fname ); + } + } + + if ( records[0]->fields[0]->len == 0 ) { + /* no MXit id provided, so this must be our own profile information */ + if ( session->profile ) + g_free( session->profile ); + session->profile = profile; + } + else { + /* display other user's profile */ + mxit_show_profile( session, mxitId, profile ); + + /* cleanup */ + g_free( profile ); + } +} + + +/*------------------------------------------------------------------------ + * Return the length of a multimedia chunk + * + * @return The actual chunk data length in bytes + */ +static int get_chunk_len( const char* chunkdata ) +{ + int* sizeptr; + + sizeptr = (int*) &chunkdata[1]; /* we skip the first byte (type field) */ + + return ntohl( *sizeptr ); +} + + +/*------------------------------------------------------------------------ + * Process a received multimedia packet. + * + * @param session The MXit session object + * @param records The packet's data records + * @param rcount The number of data records + */ +static void mxit_parse_cmd_media( struct MXitSession* session, struct record** records, int rcount ) +{ + char type; + int size; + + type = records[0]->fields[0]->data[0]; + size = get_chunk_len( records[0]->fields[0]->data ); + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_media (%i records) (%i bytes)\n", rcount, size ); + + /* supported chunked data types */ + switch ( type ) { + case CP_CHUNK_CUSTOM : /* custom resource */ + { + struct cr_chunk chunk; + + /* decode the chunked data */ + memset( &chunk, 0, sizeof( struct cr_chunk ) ); + mxit_chunk_parse_cr( &records[0]->fields[0]->data[sizeof( char ) + sizeof( int )], records[0]->fields[0]->len, &chunk ); + + purple_debug_info( MXIT_PLUGIN_ID, "chunk info id=%s handle=%s op=%i\n", chunk.id, chunk.handle, chunk.operation ); + + /* this is a splash-screen operation */ + if ( strcmp( chunk.handle, HANDLE_SPLASH2 ) == 0 ) { + if ( chunk.operation == CR_OP_UPDATE ) { /* update the splash-screen */ + struct splash_chunk *splash = chunk.resources->data; // TODO: Fix - assuming 1st resource is splash + gboolean clickable = ( g_list_length( chunk.resources ) > 1 ); // TODO: Fix - if 2 resources, then is clickable + + if ( splash != NULL ) + splash_update( session, chunk.id, splash->data, splash->datalen, clickable ); + } + else if ( chunk.operation == CR_OP_REMOVE ) /* remove the splash-screen */ + splash_remove( session ); + } + + /* cleanup custom resources */ + g_list_foreach( chunk.resources, (GFunc)g_free, NULL ); + + } + break; + + case CP_CHUNK_OFFER : /* file offer */ + { + struct offerfile_chunk chunk; + + /* decode the chunked data */ + memset( &chunk, 0, sizeof( struct offerfile_chunk ) ); + mxit_chunk_parse_offer( &records[0]->fields[0]->data[sizeof( char ) + sizeof( int )], records[0]->fields[0]->len, &chunk ); + + /* process the offer */ + mxit_xfer_rx_offer( session, chunk.username, chunk.filename, chunk.filesize, chunk.fileid ); + } + break; + + case CP_CHUNK_GET : /* get file response */ + { + struct getfile_chunk chunk; + + /* decode the chunked data */ + memset( &chunk, 0, sizeof( struct getfile_chunk ) ); + mxit_chunk_parse_get( &records[0]->fields[0]->data[sizeof( char ) + sizeof( int )], records[0]->fields[0]->len, &chunk ); + + /* process the getfile */ + mxit_xfer_rx_file( session, chunk.fileid, chunk.data, chunk.length ); + } + break; + + case CP_CHUNK_GET_AVATAR : /* get avatars */ + { + struct getavatar_chunk chunk; + + /* decode the chunked data */ + memset( &chunk, 0, sizeof ( struct getavatar_chunk ) ); + mxit_chunk_parse_get_avatar( &records[0]->fields[0]->data[sizeof( char ) + sizeof( int )], records[0]->fields[0]->len, &chunk ); + + /* update avatar image */ + if ( chunk.data ) { + purple_debug_info( MXIT_PLUGIN_ID, "updating avatar for contact '%s'\n", chunk.mxitid ); + purple_buddy_icons_set_for_user( session->acc, chunk.mxitid, g_memdup( chunk.data, chunk.length), chunk.length, chunk.avatarid ); + } + + } + break; + + case CP_CHUNK_SET_AVATAR : + /* this is a reply packet to a set avatar request. no action is required */ + break; + + case CP_CHUNK_DIRECT_SND : + /* this is a ack for a file send. no action is required */ + break; + + case CP_CHUNK_RECIEVED : + /* this is a ack for a file received. no action is required */ + break; + + default : + purple_debug_error( MXIT_PLUGIN_ID, "Unsupported chunked data packet type received (%i)\n", type ); + break; + } +} + + +/*------------------------------------------------------------------------ + * Handle a redirect sent from the MXit server. + * + * @param session The MXit session object + * @param url The redirect information + */ +static void mxit_perform_redirect( struct MXitSession* session, const char* url ) +{ + gchar** parts; + gchar** host; + int type; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_perform_redirect: %s\n", url ); + + /* tokenize the URL string */ + parts = g_strsplit( url, ";", 0 ); + + /* Part 1: protocol://host:port */ + host = g_strsplit( parts[0], ":", 4 ); + if ( strcmp( host[0], "socket" ) == 0 ) { + /* redirect to a MXit socket proxy */ + g_strlcpy( session->server, &host[1][2], sizeof( session->server ) ); + session->port = atoi( host[2] ); + } + else { + purple_connection_error( session->con, _( "Cannot perform redirect using the specified protocol" ) ); + goto redirect_fail; + } + + /* Part 2: type of redirect */ + type = atoi( parts[1] ); + if ( type == CP_REDIRECT_PERMANENT ) { + /* permanent redirect, so save new MXit server and port */ + purple_account_set_string( session->acc, MXIT_CONFIG_SERVER_ADDR, session->server ); + purple_account_set_int( session->acc, MXIT_CONFIG_SERVER_PORT, session->port ); + } + + /* Part 3: message (optional) */ + if ( parts[2] != NULL ) + purple_connection_notice( session->con, parts[2] ); + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_perform_redirect: %s redirect to %s:%i\n", + ( type == CP_REDIRECT_PERMANENT ) ? "Permanent" : "Temporary", session->server, session->port ); + + /* perform the re-connect to the new MXit server */ + mxit_reconnect( session ); + +redirect_fail: + g_strfreev( parts ); + g_strfreev( host ); +} + + +/*------------------------------------------------------------------------ + * Process a success response received from the MXit server. + * + * @param session The MXit session object + * @param packet The received packet + */ +static int process_success_response( struct MXitSession* session, struct rx_packet* packet ) +{ + /* ignore ping/poll packets */ + if ( ( packet->cmd != CP_CMD_PING ) && ( packet->cmd != CP_CMD_POLL ) ) + session->last_rx = time( NULL ); + + /* + * when we pass the packet records to the next level for parsing + * we minus 3 records because 1) the first record is the packet + * type 2) packet reply status 3) the last record is bogus + */ + + /* packet command */ + switch ( packet->cmd ) { + + case CP_CMD_REGISTER : + /* fall through, when registeration successful, MXit will auto login */ + case CP_CMD_LOGIN : + /* login response */ + if ( !( session->flags & MXIT_FLAG_LOGGEDIN ) ) { + mxit_parse_cmd_login( session, &packet->records[2], packet->rcount - 3 ); + } + break; + + case CP_CMD_LOGOUT : + /* logout response */ + session->flags &= ~MXIT_FLAG_LOGGEDIN; + purple_account_disconnect( session->acc ); + + /* note: + * we do not prompt the user here for a reconnect, because this could be the user + * logging in with his phone. so we just disconnect the account otherwise + * mxit will start to bounce between the phone and pidgin. also could be a valid + * disconnect selected by the user. + */ + return -1; + + case CP_CMD_CONTACT : + /* contact update */ + mxit_parse_cmd_contact( session, &packet->records[2], packet->rcount - 3 ); + break; + + case CP_CMD_PRESENCE : + /* presence update */ + mxit_parse_cmd_presence(session, &packet->records[2], packet->rcount - 3 ); + break; + + case CP_CMD_RX_MSG : + /* incoming message (no bogus record) */ + mxit_parse_cmd_message( session, &packet->records[2], packet->rcount - 2 ); + break; + + case CP_CMD_NEW_SUB : + /* new subscription request */ + mxit_parse_cmd_new_sub( session, &packet->records[2], packet->rcount - 3 ); + break; + + case CP_CMD_MEDIA : + /* multi-media message */ + mxit_parse_cmd_media( session, &packet->records[2], packet->rcount - 2 ); + break; + + case CP_CMD_EXTPROFILE_GET : + /* profile update */ + mxit_parse_cmd_extprofile( session, &packet->records[2], packet->rcount - 2 ); + break; + + case CP_CMD_MOOD : + /* mood update */ + case CP_CMD_UPDATE : + /* update contact information */ + case CP_CMD_ALLOW : + /* allow subscription ack */ + case CP_CMD_DENY : + /* deny subscription ack */ + case CP_CMD_INVITE : + /* invite contact ack */ + case CP_CMD_REMOVE : + /* remove contact ack */ + case CP_CMD_TX_MSG : + /* outgoing message ack */ + case CP_CMD_STATUS : + /* presence update ack */ + case CP_CMD_GRPCHAT_CREATE : + /* create groupchat */ + case CP_CMD_GRPCHAT_INVITE : + /* groupchat invite */ + case CP_CMD_PING : + /* ping reply */ + case CP_CMD_POLL : + /* HTTP poll reply */ + case CP_CMD_EXTPROFILE_SET : + /* profile update */ + case CP_CMD_SPLASHCLICK : + /* splash-screen clickthrough */ + break; + + default : + /* unknown packet */ + purple_debug_error( MXIT_PLUGIN_ID, "Received unknown client packet (cmd = %i)\n", packet->cmd ); + } + + return 0; +} + + +/*------------------------------------------------------------------------ + * Process an error response received from the MXit server. + * + * @param session The MXit session object + * @param packet The received packet + */ +static int process_error_response( struct MXitSession* session, struct rx_packet* packet ) +{ + char errmsg[256]; + const char* errdesc; + + /* set the error description to be shown to the user */ + if ( packet->errmsg ) + errdesc = packet->errmsg; + else + errdesc = "An internal MXit server error occurred."; + + purple_debug_info( MXIT_PLUGIN_ID, "Error Reply %i:%s\n", packet->errcode, errdesc ); + + if ( packet->errcode == MXIT_ERRCODE_LOGGEDOUT ) { + /* we are not currently logged in, so we need to reconnect */ + purple_connection_error( session->con, _( errmsg ) ); + } + + /* packet command */ + switch ( packet->cmd ) { + + case CP_CMD_REGISTER : + case CP_CMD_LOGIN : + if ( packet->errcode == MXIT_ERRCODE_REDIRECT ) { + mxit_perform_redirect( session, packet->errmsg ); + return 0; + } + else { + sprintf( errmsg, "Login error: %s (%i)", errdesc, packet->errcode ); + purple_connection_error( session->con, _( errmsg ) ); + return -1; + } + case CP_CMD_LOGOUT : + sprintf( errmsg, "Logout error: %s (%i)", errdesc, packet->errcode ); + purple_connection_error_reason( session->con, PURPLE_CONNECTION_ERROR_NAME_IN_USE, _( errmsg ) ); + return -1; + case CP_CMD_CONTACT : + mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Contact Error" ), _( errdesc ) ); + break; + case CP_CMD_RX_MSG : + mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Message Error" ), _( errdesc ) ); + break; + case CP_CMD_TX_MSG : + mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Message Sending Error" ), _( errdesc ) ); + break; + case CP_CMD_STATUS : + mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Status Error" ), _( errdesc ) ); + break; + case CP_CMD_MOOD : + mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Mood Error" ), _( errdesc ) ); + break; + case CP_CMD_KICK : + /* + * the MXit server sends this packet if we were idle for too long. + * to stop the server from closing this connection we need to resend + * the login packet. + */ + mxit_send_login( session ); + break; + case CP_CMD_INVITE : + mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Invitation Error" ), _( errdesc ) ); + break; + case CP_CMD_REMOVE : + mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Contact Removal Error" ), _( errdesc ) ); + break; + case CP_CMD_ALLOW : + case CP_CMD_DENY : + mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Subscription Error" ), _( errdesc ) ); + break; + case CP_CMD_UPDATE : + mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Contact Update Error" ), _( errdesc ) ); + break; + case CP_CMD_MEDIA : + mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "File Transfer Error" ), _( errdesc ) ); + break; + case CP_CMD_GRPCHAT_CREATE : + mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Cannot create MultiMx room" ), _( errdesc ) ); + break; + case CP_CMD_GRPCHAT_INVITE : + mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "MultiMx Invitation Error" ), _( errdesc ) ); + break; + case CP_CMD_EXTPROFILE_GET : + case CP_CMD_EXTPROFILE_SET : + mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Profile Error" ), _( errdesc ) ); + break; + case CP_CMD_SPLASHCLICK : + /* ignore error */ + break; + case CP_CMD_PING : + case CP_CMD_POLL : + break; + default : + mxit_popup( PURPLE_NOTIFY_MSG_ERROR, _( "Error" ), _( errdesc ) ); + break; + } + + return 0; +} + + +/*======================================================================================================================== + * Low-level Packet receive + */ + +#ifdef DEBUG_PROTOCOL +/*------------------------------------------------------------------------ + * Dump a received packet structure. + * + * @param p The received packet + */ +static void dump_packet( struct rx_packet* p ) +{ + struct record* r = NULL; + struct field* f = NULL; + int i; + int j; + + purple_debug_info( MXIT_PLUGIN_ID, "PACKET DUMP: (%i records)\n", p->rcount ); + + for ( i = 0; i < p->rcount; i++ ) { + r = p->records[i]; + purple_debug_info( MXIT_PLUGIN_ID, "RECORD: (%i fields)\n", r->fcount ); + + for ( j = 0; j < r->fcount; j++ ) { + f = r->fields[j]; + purple_debug_info( MXIT_PLUGIN_ID, "\tFIELD: (len=%i) '%s' \n", f->len, f->data ); + } + } +} +#endif + + +/*------------------------------------------------------------------------ + * Free up memory used by a packet structure. + * + * @param p The received packet + */ +static void free_rx_packet( struct rx_packet* p ) +{ + struct record* r = NULL; + struct field* f = NULL; + int i; + int j; + + for ( i = 0; i < p->rcount; i++ ) { + r = p->records[i]; + + for ( j = 0; j < r->fcount; j++ ) { + g_free( f ); + } + g_free( r->fields ); + g_free( r ); + } + g_free( p->records ); +} + + +/*------------------------------------------------------------------------ + * Add a new field to a record. + * + * @param r Parent record object + * @return The newly created field + */ +static struct field* add_field( struct record* r ) +{ + struct field* field; + + field = g_new0( struct field, 1 ); + + r->fields = realloc( r->fields, sizeof( struct field* ) * ( r->fcount + 1 ) ); + r->fields[r->fcount] = field; + r->fcount++; + + return field; +} + + +/*------------------------------------------------------------------------ + * Add a new record to a packet. + * + * @param p The packet object + * @return The newly created record + */ +static struct record* add_record( struct rx_packet* p ) +{ + struct record* rec; + + rec = g_new0( struct record, 1 ); + + p->records = realloc( p->records, sizeof( struct record* ) * ( p->rcount + 1 ) ); + p->records[p->rcount] = rec; + p->rcount++; + + return rec; +} + + +/*------------------------------------------------------------------------ + * Parse the received byte stream into a proper client protocol packet. + * + * @param session The MXit session object + * @return Success (0) or Failure (!0) + */ +int mxit_parse_packet( struct MXitSession* session ) +{ + struct rx_packet packet; + struct record* rec; + struct field* field; + gboolean pbreak; + unsigned int i; + int res = 0; + +#ifdef DEBUG_PROTOCOL + purple_debug_info( MXIT_PLUGIN_ID, "Received packet (%i bytes)\n", session->rx_i ); + dump_bytes( session, session->rx_dbuf, session->rx_i ); +#endif + + i = 0; + while ( i < session->rx_i ) { + + /* create first record and field */ + rec = NULL; + field = NULL; + memset( &packet, 0x00, sizeof( struct rx_packet ) ); + rec = add_record( &packet ); + pbreak = FALSE; + + /* break up the received packet into fields and records for easy parsing */ + while ( ( i < session->rx_i ) && ( !pbreak ) ) { + + switch ( session->rx_dbuf[i] ) { + case CP_SOCK_REC_TERM : + /* new record */ + if ( packet.rcount == 1 ) { + /* packet command */ + packet.cmd = atoi( packet.records[0]->fields[0]->data ); + } + else if ( packet.rcount == 2 ) { + /* special case: binary multimedia packets should not be parsed here */ + if ( packet.cmd == CP_CMD_MEDIA ) { + /* add the chunked to new record */ + rec = add_record( &packet ); + field = add_field( rec ); + field->data = &session->rx_dbuf[i + 1]; + field->len = session->rx_i - i; + /* now skip the binary data */ + res = get_chunk_len( field->data ); + /* determine if we have more packets */ + if ( res + 6 + i < session->rx_i ) { + /* we have more than one packet in this stream */ + i += res + 6; + pbreak = TRUE; + } + else { + i = session->rx_i; + } + } + } + else if ( !field ) { + field = add_field( rec ); + field->data = &session->rx_dbuf[i]; + } + session->rx_dbuf[i] = '\0'; + rec = add_record( &packet ); + field = NULL; + + break; + case CP_FLD_TERM : + /* new field */ + session->rx_dbuf[i] = '\0'; + if ( !field ) { + field = add_field( rec ); + field->data = &session->rx_dbuf[i]; + } + field = NULL; + break; + case CP_PKT_TERM : + /* packet is done! */ + session->rx_dbuf[i] = '\0'; + pbreak = TRUE; + break; + default : + /* skip non special characters */ + if ( !field ) { + field = add_field( rec ); + field->data = &session->rx_dbuf[i]; + } + field->len++; + break; + } + + i++; + } + + if ( packet.rcount < 2 ) { + /* bad packet */ + purple_connection_error( session->con, _( "Invalid packet received from MXit." ) ); + free_rx_packet( &packet ); + continue; + } + + session->rx_dbuf[session->rx_i] = '\0'; + packet.errcode = atoi( packet.records[1]->fields[0]->data ); + + purple_debug_info( MXIT_PLUGIN_ID, "Packet received CMD:%i (%i)\n", packet.cmd, packet.errcode ); +#ifdef DEBUG_PROTOCOL + /* debug */ + dump_packet( &packet ); +#endif + + /* reset the out ack */ + if ( session->outack == packet.cmd ) { + /* outstanding ack received from mxit server */ + session->outack = 0; + } + + /* check packet status */ + if ( packet.errcode != MXIT_ERRCODE_SUCCESS ) { + /* error reply! */ + if ( ( packet.records[1]->fcount > 1 ) && ( packet.records[1]->fields[1]->data ) ) + packet.errmsg = packet.records[1]->fields[1]->data; + else + packet.errmsg = NULL; + + res = process_error_response( session, &packet ); + } + else { + /* success reply! */ + res = process_success_response( session, &packet ); + } + + /* free up the packet resources */ + free_rx_packet( &packet ); + } + + if ( session->outack == 0 ) + mxit_manage_queue( session ); + + return res; +} + + +/*------------------------------------------------------------------------ + * Callback when data is received from the MXit server. + * + * @param user_data The MXit session object + * @param source The file-descriptor on which data was received + * @param cond Condition which caused the callback (PURPLE_INPUT_READ) + */ +void mxit_cb_rx( gpointer user_data, gint source, PurpleInputCondition cond ) +{ + struct MXitSession* session = (struct MXitSession*) user_data; + char ch; + int res; + int len; + + if ( session->rx_state == RX_STATE_RLEN ) { + /* we are reading in the packet length */ + len = read( session->fd, &ch, 1 ); + if ( len < 0 ) { + /* connection error */ + purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x01)" ) ); + return; + } + else if ( len == 0 ) { + /* connection closed */ + purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x02)" ) ); + return; + } + else { + /* byte read */ + if ( ch == CP_REC_TERM ) { + /* the end of the length record found */ + session->rx_lbuf[session->rx_i] = '\0'; + session->rx_res = atoi( &session->rx_lbuf[3] ); + if ( session->rx_res > CP_MAX_PACKET ) { + purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x03)" ) ); + } + session->rx_state = RX_STATE_DATA; + session->rx_i = 0; + } + else { + /* still part of the packet length record */ + session->rx_lbuf[session->rx_i] = ch; + session->rx_i++; + if ( session->rx_i >= sizeof( session->rx_lbuf ) ) { + /* malformed packet length record (too long) */ + purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x04)" ) ); + return; + } + } + } + } + else if ( session->rx_state == RX_STATE_DATA ) { + /* we are reading in the packet data */ + len = read( session->fd, &session->rx_dbuf[session->rx_i], session->rx_res ); + if ( len < 0 ) { + /* connection error */ + purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x05)" ) ); + return; + } + else if ( len == 0 ) { + /* connection closed */ + purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x06)" ) ); + return; + } + else { + /* data read */ + session->rx_i += len; + session->rx_res -= len; + + if ( session->rx_res == 0 ) { + /* ok, so now we have read in the whole packet */ + session->rx_state = RX_STATE_PROC; + } + } + } + + if ( session->rx_state == RX_STATE_PROC ) { + /* we have a full packet, which we now need to process */ + res = mxit_parse_packet( session ); + + if ( res == 0 ) { + /* we are still logged in */ + session->rx_state = RX_STATE_RLEN; + session->rx_res = 0; + session->rx_i = 0; + } + } +} + + +/*------------------------------------------------------------------------ + * Log the user off MXit and close the connection + * + * @param session The MXit session object + */ +void mxit_close_connection( struct MXitSession* session ) +{ + purple_debug_info( MXIT_PLUGIN_ID, "mxit_close_connection\n" ); + + if ( !( session->flags & MXIT_FLAG_CONNECTED ) ) { + /* we are already closed */ + return; + } + else if ( session->flags & MXIT_FLAG_LOGGEDIN ) { + /* we are currently logged in so we need to send a logout packet */ + if ( !session->http ) { + mxit_send_logout( session ); + } + session->flags &= ~MXIT_FLAG_LOGGEDIN; + } + session->flags &= ~MXIT_FLAG_CONNECTED; + + /* cancel outstanding HTTP request */ + if ( ( session->http ) && ( session->http_out_req ) ) { + purple_util_fetch_url_cancel( (PurpleUtilFetchUrlData*) session->http_out_req ); + session->http_out_req = NULL; + } + + /* remove the input cb function */ + if ( session->con->inpa ) { + purple_input_remove( session->con->inpa ); + session->con->inpa = 0; + } + + /* remove HTTP poll timer */ + if ( session->http_timer_id > 0 ) + purple_timeout_remove( session->http_timer_id ); + + /* remove queue manager timer */ + if ( session->q_timer > 0 ) + purple_timeout_remove( session->q_timer ); + + /* remove all groupchat rooms */ + while ( session->rooms != NULL ) { + struct multimx* multimx = (struct multimx *) session->rooms->data; + + session->rooms = g_list_remove( session->rooms, multimx ); + + free( multimx ); + } + g_list_free( session->rooms ); + session->rooms = NULL; + + /* remove all rx chats names */ + while ( session->active_chats != NULL ) { + char* chat = (char*) session->active_chats->data; + + session->active_chats = g_list_remove( session->active_chats, chat ); + + g_free( chat ); + } + g_list_free( session->active_chats ); + session->active_chats = NULL; + + /* free profile information */ + if ( session->profile ) + free( session->profile ); + + /* free custom emoticons */ + mxit_free_emoticon_cache( session ); + + /* free allocated memory */ + g_free( session->encpwd ); + session->encpwd = NULL; + + /* flush all the commands still in the queue */ + flush_queue( session ); +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/protocol.h Mon Nov 09 19:27:45 2009 +0000 @@ -0,0 +1,304 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- MXit client protocol implementation -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef _MXIT_PROTO_H_ +#define _MXIT_PROTO_H_ + + +/* Client protocol constants */ +#define CP_SOCK_REC_TERM '\x00' /* socket record terminator */ +#define CP_HTTP_REC_TERM '\x26' /* http record terminator '&' */ +#define CP_FLD_TERM '\x01' /* field terminator */ +#define CP_PKT_TERM '\x02' /* packet terminator */ + + +#define CP_MAX_PACKET ( 1024 * 1024 ) /* maximum client protocol packet size (1 MiB) */ +#define CP_MAX_FILESIZE ( 150 * 1000 ) /* maximum client protocol file transfer size (150 KB) */ +#define MXIT_EMOTICON_SIZE 18 /* icon size for custom emoticons */ +#define CP_MAX_STATUS_MSG 250 /* maximum status message length (in characters) */ + +/* Avatars */ +#define MXIT_AVATAR_SIZE 96 /* default avatar image size 96x96 */ +#define MXIT_AVATAR_TYPE "PNG" /* request avatars in this file type (only a suggestion) */ +#define MXIT_AVATAR_BITDEPT 24 /* request avatars with this bit depth (only a suggestion) */ + +/* Protocol error codes */ +#define MXIT_ERRCODE_SUCCESS 0 +#define MXIT_ERRCODE_REDIRECT 16 +#define MXIT_ERRCODE_LOGGEDOUT 42 + +/* MXit client features */ +#define MXIT_CF_NONE 0x000000 +#define MXIT_CF_FORMS 0x000001 +#define MXIT_CF_FILE_TRANSFER 0x000002 +#define MXIT_CF_CAMERA 0x000004 +#define MXIT_CF_COMMANDS 0x000008 +#define MXIT_CF_SMS 0x000010 +#define MXIT_CF_FILE_ACCESS 0x000020 +#define MXIT_CF_MIDP2 0x000040 +#define MXIT_CF_SKINS 0x000080 +#define MXIT_CF_AUDIO 0x000100 +#define MXIT_CF_ENCRYPTION 0x000200 +#define MXIT_CF_VOICE_REC 0x000400 +#define MXIT_CF_VECTOR_GFX 0x000800 +#define MXIT_CF_IMAGES 0x001000 +#define MXIT_CF_MARKUP 0x002000 +#define MXIT_CF_VIBES 0x004000 +#define MXIT_CF_SELECT_CONTACT 0x008000 +#define MXIT_CF_CUSTOM_EMO 0x010000 +#define MXIT_CF_ALERT_PROFILES 0x020000 +#define MXIT_CF_EXT_MARKUP 0x040000 +#define MXIT_CF_PLAIN_PWD 0x080000 +#define MXIT_CF_NO_GATEWAYS 0x100000 + +/* Client features supported by this implementation */ +#define MXIT_CP_FEATURES ( MXIT_CF_FILE_TRANSFER | MXIT_CF_FILE_ACCESS | MXIT_CF_AUDIO | MXIT_CF_MARKUP | MXIT_CF_EXT_MARKUP | MXIT_CF_NO_GATEWAYS | MXIT_CF_IMAGES | MXIT_CF_COMMANDS | MXIT_CF_VIBES | MXIT_CF_MIDP2 ) + + +#define MXIT_PING_INTERVAL ( 5 * 60 ) /* ping the server after X seconds of being idle (5 minutes) */ +#define MXIT_ACK_TIMEOUT ( 30 ) /* timeout after waiting X seconds for an ack from the server (30 seconds) */ + +/* MXit client version */ +#define MXIT_CP_DISTCODE "P" /* client distribution code (magic, do not touch!) */ +#define MXIT_CP_RELEASE "5.9.0" /* client protocol release version supported */ +#define MXIT_CP_ARCH "Y" /* client architecture series (Y not for Yoda but for PC-client) */ +#define MXIT_CLIENT_ID "LP" /* client ID as specified by MXit */ +#define MXIT_CP_PLATFORM "PURPLE" /* client platform */ +#define MXIT_CP_VERSION MXIT_CP_DISTCODE"-"MXIT_CP_RELEASE"-"MXIT_CP_ARCH"-"MXIT_CP_PLATFORM + +/* set operating system name */ +#if defined( __APPLE__ ) +#define MXIT_CP_OS "apple" +#elif defined( _WIN32 ) +#define MXIT_CP_OS "windows" +#elif defined( __linux__ ) +#define MXIT_CP_OS "linux" +#else +#define MXIT_CP_OS "unknown" +#endif + +/* Client capabilities */ +#define MXIT_CP_CAP "utf8=true;cid="MXIT_CLIENT_ID + +/* Client settings */ +#define MAX_QUEUE_SIZE ( 1 << 4 ) /* tx queue size (16 packets) */ +#define MXIT_POPUP_WIN_NAME "MXit Notification" /* popup window name */ +#define MXIT_MAX_ATTRIBS 10 /* maximum profile attributes supported */ +#define MXIT_DEFAULT_LOCALE "en" /* default locale setting */ +#define MXIT_DEFAULT_LOC "planetpurple" /* the default location for registration */ + +/* Client protocol commands */ +#define CP_CMD_LOGIN 0x0001 /* (1) login */ +#define CP_CMD_LOGOUT 0x0002 /* (2) logout */ +#define CP_CMD_CONTACT 0x0003 /* (3) get contacts */ +#define CP_CMD_UPDATE 0x0005 /* (5) update contact information */ +#define CP_CMD_INVITE 0x0006 /* (6) subscribe to new contact */ +#define CP_CMD_PRESENCE 0x0007 /* (7) get presence */ +#define CP_CMD_REMOVE 0x0008 /* (8) remove contact */ +#define CP_CMD_RX_MSG 0x0009 /* (9) get new messages */ +#define CP_CMD_TX_MSG 0x000A /* (10) send new message */ +#define CP_CMD_REGISTER 0x000B /* (11) register */ +//#define CP_CMD_PROFILE_SET 0x000C /* (12) set profile (DEPRECATED see CP_CMD_EXTPROFILE_SET) */ +#define CP_CMD_POLL 0x0011 /* (17) poll the HTTP server for an update */ +//#define CP_CMD_PROFILE_GET 0x001A /* (26) get profile (DEPRECATED see CP_CMD_EXTPROFILE_GET) */ +#define CP_CMD_MEDIA 0x001B /* (27) get multimedia message */ +#define CP_CMD_SPLASHCLICK 0x001F /* (31) splash-screen clickthrough */ +#define CP_CMD_STATUS 0x0020 /* (32) set shown presence & status */ +#define CP_CMD_MOOD 0x0029 /* (41) set mood */ +#define CP_CMD_KICK 0x002B /* (43) login kick */ +#define CP_CMD_GRPCHAT_CREATE 0x002C /* (44) create new groupchat */ +#define CP_CMD_GRPCHAT_INVITE 0x002D /* (45) add new groupchat member */ +#define CP_CMD_NEW_SUB 0x0033 /* (51) get new subscription */ +#define CP_CMD_ALLOW 0x0034 /* (52) allow subscription */ +#define CP_CMD_DENY 0x0037 /* (55) deny subscription */ +#define CP_CMD_EXTPROFILE_GET 0x0039 /* (57) get extended profile */ +#define CP_CMD_EXTPROFILE_SET 0x003A /* (58) set extended profile */ +#define CP_CMD_PING 0x03E8 /* (1000) ping (keepalive) */ + +/* HTTP connection */ +#define MXIT_HTTP_POLL_MIN 7 /* minimum time between HTTP polls (seconds) */ +#define MXIT_HTTP_POLL_MAX ( 10 * 60 ) /* maximum time between HTTP polls (seconds) */ + +/* receiver states */ +#define RX_STATE_RLEN 0x01 /* reading packet length section */ +#define RX_STATE_DATA 0x02 /* reading packet data section */ +#define RX_STATE_PROC 0x03 /* process read data */ + +/* message flags */ +#define CP_MSG_ENCRYPTED 0x0010 /* message is encrypted */ +#define CP_MSG_MARKUP 0x0200 /* message may contain markup */ +#define CP_MSG_EMOTICON 0x0400 /* message may contain custom emoticons */ + +/* redirect types */ +#define CP_REDIRECT_PERMANENT 1 /* permanent redirect */ +#define CP_REDIRECT_TEMPORARY 2 /* temporary redirect */ + +/* message tx types */ +#define CP_MSGTYPE_NORMAL 0x01 /* normal message */ +#define CP_MSGTYPE_CHAT 0x02 /* chat message */ +#define CP_MSGTYPE_HEADLINE 0x03 /* headline message */ +#define CP_MSGTYPE_ERROR 0x04 /* error message */ +#define CP_MSGTYPE_GROUPCHAT 0x05 /* groupchat message */ +#define CP_MSGTYPE_FORM 0x06 /* mxit custom form */ +#define CP_MSGTYPE_COMMAND 0x07 /* mxit command */ + + +/* extended profile attribute fields */ +#define CP_PROFILE_BIRTHDATE "birthdate" /* Birthdate (String - ISO 8601 format) */ +#define CP_PROFILE_GENDER "gender" /* Gender (Boolean - 0=female, 1=male) */ +#define CP_PROFILE_HIDENUMBER "hidenumber" /* Hide Number (Boolean - 0=false, 1=true) */ +#define CP_PROFILE_FULLNAME "fullname" /* Fullname (UTF8 String) */ +#define CP_PROFILE_STATUS "statusmsg" /* Status Message (UTF8 String) */ +#define CP_PROFILE_PREVSTATUS "prevstatusmsgs" /* Previous Status Messages (UTF8 String) */ +#define CP_PROFILE_AVATAR "avatarid" /* Avatar ID (String) */ +#define CP_PROFILE_MODIFIED "lastmodified" /* Last-Modified timestamp */ +#define CP_PROFILE_TITLE "title" /* Title (UTF8 String) */ +#define CP_PROFILE_FIRSTNAME "firstname" /* First name (UTF8 String) */ +#define CP_PROFILE_LASTNAME "lastname" /* Last name (UTF8 String) */ +#define CP_PROFILE_EMAIL "email" /* Email address (UTF8 String) */ +#define CP_PROFILE_MOBILENR "mobilenumber" /* Mobile Number (UTF8 String) */ + +/* extended profile field types */ +#define CP_PROF_TYPE_BOOL 0x02 /* boolean profile attribute type */ +#define CP_PROF_TYPE_INT 0x05 /* integer profile attribute type */ +#define CP_PROF_TYPE_UTF8 0x0A /* UTF8 string profile attribute type */ +#define CP_PROF_TYPE_DATE 0x0B /* date-time profile attribute type */ + + +/* define this to enable protocol debugging (very verbose logging) */ +#define DEBUG_PROTOCOL + + +/* ======================================================================================= */ + +struct MXitSession; + +/*------------------------------------------*/ + +struct field { + char* data; + int len; +}; + +struct record { + struct field** fields; + int fcount; +}; + +struct rx_packet { + int cmd; + int errcode; + char* errmsg; + struct record** records; + int rcount; +}; + +struct tx_packet { + int cmd; + char header[256]; + int headerlen; + char* data; + int datalen; +}; + +/*------------------------------------------*/ + + +/* + * A received message data object + */ +struct RXMsgData { + struct MXitSession* session; /* MXit session object */ + char* from; /* the sender's name */ + time_t timestamp; /* time at which the message was sent */ + GString* msg; /* newly created message converted to libPurple formatting */ + gboolean got_img; /* flag to say if this message got any images/emoticons embedded */ + short img_count; /* the amount of images/emoticons still outstanding for the message */ + int chatid; /* multimx chatroom id */ + int flags; /* libPurple conversation flags */ + gboolean converted; /* true if the message has been completely parsed and converted to libPurple markup */ + gboolean processed; /* the message has been processed completely and should be freed up */ +}; + + + +/* + * The packet transmission queue. + */ +struct tx_queue { + struct tx_packet* packets[MAX_QUEUE_SIZE]; /* array of packet pointers */ + int count; /* number of packets queued */ + int rd_i; /* queue current read index (queue offset for reading a packet) */ + int wr_i; /* queue current write index (queue offset for adding new packet) */ +}; + + +/* ======================================================================================= */ + +void mxit_popup( int type, const char* heading, const char* message ); +void mxit_strip_domain( char* username ); +gboolean find_active_chat( const GList* chats, const char* who ); + +void mxit_cb_rx( gpointer data, gint source, PurpleInputCondition cond ); +gboolean mxit_manage_queue( gpointer user_data ); +gboolean mxit_manage_polling( gpointer user_data ); + +void mxit_send_register( struct MXitSession* session ); +void mxit_send_login( struct MXitSession* session ); +void mxit_send_logout( struct MXitSession* session ); +void mxit_send_ping( struct MXitSession* session ); +void mxit_send_poll( struct MXitSession* session ); + +void mxit_send_presence( struct MXitSession* session, int presence, const char* statusmsg ); +void mxit_send_mood( struct MXitSession* session, int mood ); +void mxit_send_message( struct MXitSession* session, const char* to, const char* msg, gboolean parse_markup ); + +void mxit_send_extprofile_update( struct MXitSession* session, const char* password, unsigned int nr_attrib, const char* attributes ); +void mxit_send_extprofile_request( struct MXitSession* session, const char* username, unsigned int nr_attrib, const char* attribute[] ); + +void mxit_send_invite( struct MXitSession* session, const char* username, const char* alias, const char* groupname ); +void mxit_send_remove( struct MXitSession* session, const char* username ); +void mxit_send_allow_sub( struct MXitSession* session, const char* username, const char* alias ); +void mxit_send_deny_sub( struct MXitSession* session, const char* username ); +void mxit_send_update_contact( struct MXitSession* session, const char* username, const char* alias, const char* groupname ); +void mxit_send_splashclick( struct MXitSession* session, const char* splashid ); + +void mxit_send_file( struct MXitSession* session, const char* username, const char* filename, const unsigned char* buf, int buflen ); +void mxit_send_file_reject( struct MXitSession* session, const char* fileid ); +void mxit_send_file_accept( struct MXitSession* session, const char* fileid, int filesize, int offset ); +void mxit_send_file_received( struct MXitSession* session, const char* fileid, short status ); +void mxit_set_avatar( struct MXitSession* session, const unsigned char* avatar, int avatarlen ); +void mxit_get_avatar( struct MXitSession* session, const char* mxitId, const char* avatarId ); + +void mxit_send_groupchat_create( struct MXitSession* session, const char* groupname, int nr_usernames, const char* usernames[] ); +void mxit_send_groupchat_invite( struct MXitSession* session, const char* roomid, int nr_usernames, const char* usernames[] ); + +int mxit_parse_packet( struct MXitSession* session ); +void dump_bytes( struct MXitSession* session, const char* buf, int len ); +void mxit_close_connection( struct MXitSession* session ); + + +#endif /* _MXIT_PROTO_H_ */ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/roster.c Mon Nov 09 19:27:45 2009 +0000 @@ -0,0 +1,722 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- user roster management (mxit contacts) -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include <stdio.h> +#include <unistd.h> +#include <string.h> + +#include "purple.h" + +#include "protocol.h" +#include "mxit.h" +#include "roster.h" + + +struct contact_invite { + struct MXitSession* session; /* MXit session object */ + struct contact* contact; /* The contact performing the invite */ +}; + + +/*======================================================================================================================== + * Presence / Status + */ + +/* statuses (reference: libpurple/status.h) */ +static struct status +{ + PurpleStatusPrimitive primative; + int mxit; + const char* id; + const char* name; +} const mxit_statuses[] = { + /* primative, no, id, name */ + { PURPLE_STATUS_OFFLINE, MXIT_PRESENCE_OFFLINE, "offline", "Offline" }, /* 0 */ + { PURPLE_STATUS_AVAILABLE, MXIT_PRESENCE_ONLINE, "online", "Available" }, /* 1 */ + { PURPLE_STATUS_AWAY, MXIT_PRESENCE_AWAY, "away", "Away" }, /* 2 */ + { PURPLE_STATUS_AVAILABLE, MXIT_PRESENCE_AVAILABLE, "chat", "Chatty" }, /* 3 */ + { PURPLE_STATUS_UNAVAILABLE, MXIT_PRESENCE_DND, "dnd", "Do Not Disturb" } /* 4 */ +}; + + +/*------------------------------------------------------------------------ + * Return list of supported statuses. (see status.h) + * + * @param account The MXit account object + * @return List of PurpleStatusType + */ +GList* mxit_status_types( PurpleAccount* account ) +{ + GList* statuslist = NULL; + PurpleStatusType* type; + unsigned int i; + + for ( i = 0; i < ARRAY_SIZE( mxit_statuses ); i++ ) { + const struct status* status = &mxit_statuses[i]; + + /* add mxit status (reference: "libpurple/status.h") */ + type = purple_status_type_new_with_attrs( status->primative, status->id, status->name, TRUE, TRUE, FALSE, + "message", _( "Message" ), purple_value_new( PURPLE_TYPE_STRING ), + NULL ); + + statuslist = g_list_append( statuslist, type ); + } + + return statuslist; +} + + +/*------------------------------------------------------------------------ + * Returns the MXit presence code, given the unique status ID. + * + * @param id The status ID + * @return The MXit presence code + */ +int mxit_convert_presence( const char* id ) +{ + unsigned int i; + + for ( i = 0; i < ARRAY_SIZE( mxit_statuses ); i++ ) { + if ( strcmp( mxit_statuses[i].id, id ) == 0 ) /* status found! */ + return mxit_statuses[i].mxit; + } + + return -1; +} + + +/*------------------------------------------------------------------------ + * Returns the MXit presence as a string, given the MXit presence ID. + * + * @param no The MXit presence I (see above) + * @return The presence as a text string + */ +const char* mxit_convert_presence_to_name( short no ) +{ + unsigned int i; + + for ( i = 0; i < ARRAY_SIZE( mxit_statuses ); i++ ) { + if ( mxit_statuses[i].mxit == no ) /* status found! */ + return _( mxit_statuses[i].name ); + } + + return ""; +} + + +/*======================================================================================================================== + * Moods + */ + +/*------------------------------------------------------------------------ + * Returns the MXit mood as a string, given the MXit mood's ID. + * + * @param id The MXit mood ID (see roster.h) + * @return The mood as a text string + */ +const char* mxit_convert_mood_to_name( short id ) +{ + switch ( id ) { + case MXIT_MOOD_ANGRY : + return _( "Angry" ); + case MXIT_MOOD_EXCITED : + return _( "Excited" ); + case MXIT_MOOD_GRUMPY : + return _( "Grumpy" ); + case MXIT_MOOD_HAPPY : + return _( "Happy" ); + case MXIT_MOOD_INLOVE : + return _( "In Love" ); + case MXIT_MOOD_INVINCIBLE : + return _( "Invincible" ); + case MXIT_MOOD_SAD : + return _( "Sad" ); + case MXIT_MOOD_HOT : + return _( "Hot" ); + case MXIT_MOOD_SICK : + return _( "Sick" ); + case MXIT_MOOD_SLEEPY : + return _( "Sleepy" ); + case MXIT_MOOD_NONE : + default : + return ""; + } +} + + +/*======================================================================================================================== + * Subscription Types + */ + +/*------------------------------------------------------------------------ + * Returns a Contact subscription type as a string. + * + * @param subtype The subscription type + * @return The subscription type as a text string + */ +const char* mxit_convert_subtype_to_name( short subtype ) +{ + switch ( subtype ) { + case MXIT_SUBTYPE_BOTH : + return _( "Both" ); + case MXIT_SUBTYPE_PENDING : + return _( "Pending" ); + case MXIT_SUBTYPE_ASK : + return _( "Invited" ); + case MXIT_SUBTYPE_REJECTED : + return _( "Rejected" ); + case MXIT_SUBTYPE_DELETED : + return _( "Deleted" ); + case MXIT_SUBTYPE_NONE : + return _( "None" ); + default : + return ""; + } +} + + +/*======================================================================================================================== + * Calls from the MXit Protocol layer + */ + +#if 0 +/*------------------------------------------------------------------------ + * Dump a contact's info the the debug console. + * + * @param contact The contact + */ +static void dump_contact( struct contact* contact ) +{ + purple_debug_info( MXIT_PLUGIN_ID, "CONTACT: name='%s', alias='%s', group='%s', type='%i', presence='%i', mood='%i'\n", + contact->username, contact->alias, contact->groupname, contact->type, contact->presence, contact->mood ); +} +#endif + + +#if 0 +/*------------------------------------------------------------------------ + * Move a buddy from one group to another + * + * @param buddy the buddy to move between groups + * @param group the new group to move the buddy to + */ +static PurpleBuddy* mxit_update_buddy_group( struct MXitSession* session, PurpleBuddy* buddy, PurpleGroup* group ) +{ + struct contact* contact = NULL; + PurpleGroup* current_group = purple_buddy_get_group( buddy ); + PurpleBuddy* newbuddy = NULL; + + /* make sure the groups actually differs */ + if ( strcmp( current_group->name, group->name ) != 0 ) { + /* groupnames does not match, so we need to make the update */ + + purple_debug_info( MXIT_PLUGIN_ID, "Moving '%s' from group '%s' to '%s'\n", buddy->alias, current_group->name, group->name ); + + /* + * XXX: libPurple does not currently provide an API to change or rename the group name + * for a specific buddy. One option is to remove the buddy from the list and re-adding + * him in the new group, but by doing that makes the buddy go offline and then online + * again. This is really not ideal and very iretating, but how else then? + */ + + /* create new buddy */ + newbuddy = purple_buddy_new( session->acc, buddy->name, buddy->alias ); + newbuddy->proto_data = buddy->proto_data; + buddy->proto_data = NULL; + + /* remove the buddy */ + purple_blist_remove_buddy( buddy ); + + /* add buddy */ + purple_blist_add_buddy( newbuddy, NULL, group, NULL ); + + /* now re-instate his presence again */ + contact = newbuddy->proto_data; + if ( contact ) { + + /* update the buddy's status (reference: "libpurple/prpl.h") */ + if ( contact->statusMsg ) + purple_prpl_got_user_status( session->acc, newbuddy->name, mxit_statuses[contact->presence].id, "message", contact->statusMsg, NULL ); + else + purple_prpl_got_user_status( session->acc, newbuddy->name, mxit_statuses[contact->presence].id, NULL ); + + /* update avatar */ + if ( contact->avatarId ) { + mxit_get_avatar( session, newbuddy->name, contact->avatarId ); + g_free( contact->avatarId ); + contact->avatarId = NULL; + } + } + + return newbuddy; + } + else + return buddy; +} +#endif + + +/*------------------------------------------------------------------------ + * A contact update packet was received from the MXit server, so update the buddy's + * information. + * + * @param session The MXit session object + * @param contact The contact + */ +void mxit_update_contact( struct MXitSession* session, struct contact* contact ) +{ + PurpleBuddy* buddy = NULL; + PurpleGroup* group = NULL; + const char* id = NULL; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_update_contact: user='%s' alias='%s' group='%s'\n", contact->username, contact->alias, contact->groupname ); + + /* + * libPurple requires all contacts to be in a group. + * So if this MXit contact isn't in a group, pretend it is. + */ + if ( *contact->groupname == '\0' ) { + strcpy( contact->groupname, MXIT_DEFAULT_GROUP ); + } + + /* find or create a group for this contact */ + group = purple_find_group( contact->groupname ); + if ( !group ) + group = purple_group_new( contact->groupname ); + + /* see if the buddy is not in the group already */ + buddy = purple_find_buddy_in_group( session->acc, contact->username, group ); + if ( !buddy ) { + /* buddy not found in the group */ + + /* lets try finding him in all groups */ + buddy = purple_find_buddy( session->acc, contact->username ); + if ( buddy ) { + /* ok, so we found him in another group. to switch him between groups we must delete him and add him again. */ + purple_blist_remove_buddy( buddy ); + buddy = NULL; + } + + /* create new buddy */ + buddy = purple_buddy_new( session->acc, contact->username, contact->alias ); + buddy->proto_data = contact; + + /* add new buddy to list */ + purple_blist_add_buddy( buddy, NULL, group, NULL ); + } + else { + /* buddy was found in the group */ + + /* now update the buddy's alias */ + purple_blist_alias_buddy( buddy, contact->alias ); + + /* replace the buddy's contact struct */ + if ( buddy->proto_data ) + free( buddy->proto_data ); + buddy->proto_data = contact; + } + + /* load buddy's avatar id */ + id = purple_buddy_icons_get_checksum_for_user( buddy ); + if ( id ) + contact->avatarId = g_strdup( id ); + else + contact->avatarId = NULL; + + /* update the buddy's status (reference: "libpurple/prpl.h") */ + purple_prpl_got_user_status( session->acc, contact->username, mxit_statuses[contact->presence].id, NULL ); +} + + +/*------------------------------------------------------------------------ + * A presence update packet was received from the MXit server, so update the buddy's + * information. + * + * @param session The MXit session object + * @param username The contact which presence to update + * @param presence The new presence state for the contact + * @param mood The new mood for the contact + * @param customMood The custom mood identifier + * @param statusMsg This is the contact's status message + * @param avatarId This is the contact's avatar id + */ +void mxit_update_buddy_presence( struct MXitSession* session, const char* username, short presence, short mood, const char* customMood, const char* statusMsg, const char* avatarId ) +{ + PurpleBuddy* buddy = NULL; + struct contact* contact = NULL; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_update_buddy_presence: user='%s' presence=%i mood=%i customMood='%s' statusMsg='%s' avatar='%s'\n", + username, presence, mood, customMood, statusMsg, avatarId ); + + if ( ( presence < MXIT_PRESENCE_OFFLINE ) || ( presence > MXIT_PRESENCE_DND ) ) { + purple_debug_info( MXIT_PLUGIN_ID, "mxit_update_buddy_presence: invalid presence state %i\n", presence ); + return; /* ignore packet */ + } + + /* find the buddy information for this contact (reference: "libpurple/blist.h") */ + buddy = purple_find_buddy( session->acc, username ); + if ( !buddy ) { + purple_debug_warning( MXIT_PLUGIN_ID, "mxit_update_buddy_presence: unable to find the buddy '%s'\n", username ); + return; + } + + contact = buddy->proto_data; + if ( !contact ) + return; + + contact->presence = presence; + contact->mood = mood; + + g_strlcpy( contact->customMood, customMood, sizeof( contact->customMood ) ); + // TODO: Download custom mood frame. + + /* update status message */ + if ( contact->statusMsg ) { + g_free( contact->statusMsg ); + contact->statusMsg = NULL; + } + if ( statusMsg[0] != '\0' ) + contact->statusMsg = g_strdup( statusMsg ); + + /* update avatarId */ + if ( ( contact->avatarId ) && ( g_ascii_strcasecmp( contact->avatarId, avatarId ) == 0 ) ) { + /* avatar has not changed - do nothing */ + } + else if ( avatarId[0] != '\0' ) { /* avatar has changed */ + if ( contact->avatarId ) + g_free( contact->avatarId ); + contact->avatarId = g_strdup( avatarId ); + + /* Send request to download new avatar image */ + mxit_get_avatar( session, username, avatarId ); + } + else /* clear current avatar */ + purple_buddy_icons_set_for_user( session->acc, username, NULL, 0, NULL ); + + /* update the buddy's status (reference: "libpurple/prpl.h") */ + if ( contact->statusMsg ) + purple_prpl_got_user_status( session->acc, username, mxit_statuses[contact->presence].id, "message", contact->statusMsg, NULL ); + else + purple_prpl_got_user_status( session->acc, username, mxit_statuses[contact->presence].id, NULL ); +} + + +/*------------------------------------------------------------------------ + * update the blist cached by libPurple. We need to do this to keep + * libPurple and MXit's rosters in sync with each other. + * + * @param session The MXit session object + */ +void mxit_update_blist( struct MXitSession* session ) +{ + PurpleBuddy* buddy = NULL; + GSList* list = NULL; + unsigned int i; + + /* remove all buddies we did not receive a roster update for. + * these contacts must have been removed from another client */ + list = purple_find_buddies( session->acc, NULL ); + + for ( i = 0; i < g_slist_length( list ); i++ ) { + buddy = g_slist_nth_data( list, i ); + + if ( !buddy->proto_data ) { + /* this buddy should be removed, because we did not receive him in our roster update from MXit */ + purple_debug_info( MXIT_PLUGIN_ID, "Removed 'old' buddy from the blist '%s' (%s)\n", buddy->alias, buddy->name ); + purple_blist_remove_buddy( buddy ); + } + } + + /* tell the UI to update the blist */ + purple_blist_add_account( session->acc ); +} + + +/*------------------------------------------------------------------------ + * The user authorized an invite (subscription request). + * + * @param user_data Object associated with the invite + */ +static void mxit_cb_buddy_auth( gpointer user_data ) +{ + struct contact_invite* invite = (struct contact_invite*) user_data; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_buddy_auth '%s'\n", invite->contact->username ); + + /* send a allow subscription packet to MXit */ + mxit_send_allow_sub( invite->session, invite->contact->username, invite->contact->alias ); + + /* freeup invite object */ + if ( invite->contact->msg ) + g_free( invite->contact->msg ); + g_free( invite->contact ); + g_free( invite ); +} + + +/*------------------------------------------------------------------------ + * The user rejected an invite (subscription request). + * + * @param user_data Object associated with the invite + */ +static void mxit_cb_buddy_deny( gpointer user_data ) +{ + struct contact_invite* invite = (struct contact_invite*) user_data; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_buddy_deny '%s'\n", invite->contact->username ); + + /* send a deny subscription packet to MXit */ + mxit_send_deny_sub( invite->session, invite->contact->username ); + + /* freeup invite object */ + if ( invite->contact->msg ) + g_free( invite->contact->msg ); + g_free( invite->contact ); + g_free( invite ); +} + + +/*------------------------------------------------------------------------ + * A new subscription request packet was received from the MXit server. + * Prompt user to accept or reject it. + * + * @param session The MXit session object + * @param contact The contact performing the invite + */ +void mxit_new_subscription( struct MXitSession* session, struct contact* contact ) +{ + struct contact_invite* invite; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_new_subscription from '%s' (%s)\n", contact->username, contact->alias ); + + invite = g_new0( struct contact_invite, 1 ); + invite->session = session; + invite->contact = contact; + + /* (reference: "libpurple/account.h") */ + purple_account_request_authorization( session->acc, contact->username, NULL, contact->alias, contact->msg, FALSE, mxit_cb_buddy_auth, mxit_cb_buddy_deny, invite ); +} + + +/*------------------------------------------------------------------------ + * Return TRUE if this is a MXit Chatroom contact. + * + * @param session The MXit session object + * @param username The username of the contact + */ +gboolean is_mxit_chatroom_contact( struct MXitSession* session, const char* username ) +{ + PurpleBuddy* buddy; + struct contact* contact = NULL; + + /* find the buddy */ + buddy = purple_find_buddy( session->acc, username ); + if ( !buddy ) { + purple_debug_warning( MXIT_PLUGIN_ID, "is_mxit_chatroom_contact: unable to find the buddy '%s'\n", username ); + return FALSE; + } + + contact = buddy->proto_data; + if ( !contact ) + return FALSE; + + return ( contact->type == MXIT_TYPE_CHATROOM ); +} + + +/*======================================================================================================================== + * Callbacks from libpurple + */ + +/*------------------------------------------------------------------------ + * The user has added a buddy to the list, so send an invite request. + * + * @param gc The connection object + * @param buddy The new buddy + * @param group The group of the new buddy + */ +void mxit_add_buddy( PurpleConnection* gc, PurpleBuddy* buddy, PurpleGroup* group ) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + GSList* list = NULL; + PurpleBuddy* mxbuddy = NULL; + unsigned int i; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_add_buddy '%s' (group='%s')\n", buddy->name, group->name ); + + list = purple_find_buddies( session->acc, buddy->name ); + if ( g_slist_length( list ) == 1 ) { + purple_debug_info( MXIT_PLUGIN_ID, "mxit_add_buddy (scenario 1) (list:%i)\n", g_slist_length( list ) ); + /* + * we only send an invite to MXit when the user is not already inside our + * blist. this is done because purple does an add_buddy() call when + * you accept an invite. so in that case the user is already + * in our blist and ready to be chatted to. + */ + mxit_send_invite( session, buddy->name, buddy->alias, group->name ); + } + else { + purple_debug_info( MXIT_PLUGIN_ID, "mxit_add_buddy (scenario 2) (list:%i)\n", g_slist_length( list ) ); + /* + * we already have the buddy in our list, so we will only update + * his information here and not send another invite message + */ + + /* find the correct buddy */ + for ( i = 0; i < g_slist_length( list ); i++ ) { + mxbuddy = g_slist_nth_data( list, i ); + + if ( mxbuddy->proto_data != NULL ) { + /* this is our REAL MXit buddy! */ + + /* now update the buddy's alias */ + purple_blist_alias_buddy( mxbuddy, buddy->alias ); + + /* now update the buddy's group */ +// mxbuddy = mxit_update_buddy_group( session, mxbuddy, group ); + + /* send the update to the MXit server */ + mxit_send_update_contact( session, mxbuddy->name, mxbuddy->alias, group->name ); + } + } + } + + /* + * we remove the buddy here from the buddy list because the MXit server + * will send us a proper contact update packet if this succeeds. now + * we do not have to worry about error handling in case of adding an + * invalid contact. so the user will still see the contact as offline + * until he eventually accepts the invite. + */ + purple_blist_remove_buddy( buddy ); + + g_slist_free( list ); +} + + +/*------------------------------------------------------------------------ + * The user has removed a buddy from the list. + * + * @param gc The connection object + * @param buddy The buddy being removed + * @param group The group the buddy was in + */ +void mxit_remove_buddy( PurpleConnection* gc, PurpleBuddy* buddy, PurpleGroup* group ) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_remove_buddy '%s'\n", buddy->name ); + + mxit_send_remove( session, buddy->name ); +} + + +/*------------------------------------------------------------------------ + * The user changed the buddy's alias. + * + * @param gc The connection object + * @param who The username of the buddy + * @param alias The new alias + */ +void mxit_buddy_alias( PurpleConnection* gc, const char* who, const char* alias ) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + PurpleBuddy* buddy = NULL; + PurpleGroup* group = NULL; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_buddy_alias '%s' to '%s\n", who, alias ); + + /* find the buddy */ + buddy = purple_find_buddy( session->acc, who ); + if ( !buddy ) { + purple_debug_warning( MXIT_PLUGIN_ID, "mxit_buddy_alias: unable to find the buddy '%s'\n", who ); + return; + } + + /* find buddy group */ + group = purple_buddy_get_group( buddy ); + if ( !group ) { + purple_debug_warning( MXIT_PLUGIN_ID, "mxit_buddy_alias: unable to find the group for buddy '%s'\n", who ); + return; + } + + mxit_send_update_contact( session, who, alias, group->name ); +} + + +/*------------------------------------------------------------------------ + * The user changed the group for a single buddy. + * + * @param gc The connection object + * @param who The username of the buddy + * @param old_group The old group's name + * @param new_group The new group's name + */ +void mxit_buddy_group( PurpleConnection* gc, const char* who, const char* old_group, const char* new_group ) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + PurpleBuddy* buddy = NULL; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_buddy_group from '%s' to '%s'\n", old_group, new_group ); + + /* find the buddy */ + buddy = purple_find_buddy( session->acc, who ); + if ( !buddy ) { + purple_debug_warning( MXIT_PLUGIN_ID, "mxit_buddy_group: unable to find the buddy '%s'\n", who ); + return; + } + + mxit_send_update_contact( session, who, buddy->alias, new_group ); +} + + +/*------------------------------------------------------------------------ + * The user has selected to rename a group, so update all contacts in that + * group. + * + * @param gc The connection object + * @param old_name The old group name + * @param group The updated group object + * @param moved_buddies The buddies affected by the rename + */ +void mxit_rename_group( PurpleConnection* gc, const char* old_name, PurpleGroup* group, GList* moved_buddies ) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + PurpleBuddy* buddy = NULL; + GList* item = NULL; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_rename_group from '%s' to '%s\n", old_name, group->name ); + + // TODO: Might be more efficient to use the "rename group" command (cmd=29). + + /* loop through all the contacts in the group and send updates */ + item = moved_buddies; + while ( item ) { + buddy = item->data; + mxit_send_update_contact( session, buddy->name, buddy->alias, group->name ); + item = g_list_next( item ); + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/roster.h Mon Nov 09 19:27:45 2009 +0000 @@ -0,0 +1,139 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- user roster management (mxit contacts) -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef _MXIT_ROSTER_H_ +#define _MXIT_ROSTER_H_ + + +/* MXit contact presence states */ +#define MXIT_PRESENCE_OFFLINE 0x00 +#define MXIT_PRESENCE_ONLINE 0x01 +#define MXIT_PRESENCE_AWAY 0x02 +#define MXIT_PRESENCE_AVAILABLE 0x03 +#define MXIT_PRESENCE_DND 0x04 + + +/* MXit contact types */ +#define MXIT_TYPE_MXIT 0x00 +#define MXIT_TYPE_JABBER 0x01 +#define MXIT_TYPE_MSN 0x02 +#define MXIT_TYPE_YAHOO 0x03 +#define MXIT_TYPE_ICQ 0x04 +#define MXIT_TYPE_AIM 0x05 +#define MXIT_TYPE_QQ 0x06 +#define MXIT_TYPE_WV 0x07 +#define MXIT_TYPE_BOT 0x08 +#define MXIT_TYPE_CHATROOM 0x09 +#define MXIT_TYPE_SMS 0x0A +#define MXIT_TYPE_GROUP 0x0B +#define MXIT_TYPE_GALLERY 0x0C +#define MXIT_TYPE_INFO 0x0D +#define MXIT_TYPE_MULTIMX 0x0E +#define MXIT_TYPE_HYBRID 0x0F + + +/* MXit contact moods */ +#define MXIT_MOOD_NONE 0x00 +#define MXIT_MOOD_ANGRY 0x01 +#define MXIT_MOOD_EXCITED 0x02 +#define MXIT_MOOD_GRUMPY 0x03 +#define MXIT_MOOD_HAPPY 0x04 +#define MXIT_MOOD_INLOVE 0x05 +#define MXIT_MOOD_INVINCIBLE 0x06 +#define MXIT_MOOD_SAD 0x07 +#define MXIT_MOOD_HOT 0x08 +#define MXIT_MOOD_SICK 0x09 +#define MXIT_MOOD_SLEEPY 0x0A + + +/* MXit contact flags */ +#define MXIT_CFLAG_HIDDEN 0x02 +#define MXIT_CFLAG_GATEWAY 0x04 +#define MXIT_CFLAG_FOCUS_SEND_BLANK 0x20000 + + +/* Subscription types */ +#define MXIT_SUBTYPE_BOTH 'B' +#define MXIT_SUBTYPE_PENDING 'P' +#define MXIT_SUBTYPE_ASK 'A' +#define MXIT_SUBTYPE_REJECTED 'R' +#define MXIT_SUBTYPE_DELETED 'D' +#define MXIT_SUBTYPE_NONE 'N' + + +/* client protocol constants */ +#define MXIT_CP_MAX_JID_LEN 64 +#define MXIT_CP_MAX_GROUP_LEN 32 +#define MXIT_CP_MAX_ALIAS_LEN 48 + +#define MXIT_DEFAULT_GROUP "MXit" + + +/* + * a MXit contact + */ +struct contact { + char username[MXIT_CP_MAX_JID_LEN+1]; /* unique contact name (with domain) */ + char alias[MXIT_CP_MAX_GROUP_LEN+1]; /* contact alias (what will be seen) */ + char groupname[MXIT_CP_MAX_ALIAS_LEN+1]; /* contact group name */ + + short type; /* contact type */ + short mood; /* contact current mood */ + int flags; /* contact flags */ + short presence; /* presence state */ + short subtype; /* subscription type */ + + char* msg; /* invite message */ + + char customMood[16]; /* custom mood */ + char* statusMsg; /* status message */ + char* avatarId; /* avatarId */ +}; + +/* Presence / Status */ +GList* mxit_status_types( PurpleAccount* account ); +int mxit_convert_presence( const char* id ); +const char* mxit_convert_presence_to_name( short no ); +const char* mxit_convert_subtype_to_name( short subtype ); + +/* Moods */ +const char* mxit_convert_mood_to_name( short id ); + +/* MXit Protocol callbacks */ +void mxit_update_contact( struct MXitSession* session, struct contact* contact ); +void mxit_update_buddy_presence( struct MXitSession* session, const char* username, short presence, short mood, const char* customMood, const char* statusMsg, const char* avatarId ); +void mxit_new_subscription( struct MXitSession* session, struct contact* contact ); +void mxit_update_blist( struct MXitSession* session ); +gboolean is_mxit_chatroom_contact( struct MXitSession* session, const char* username ); + +/* libPurple callbacks */ +void mxit_add_buddy( PurpleConnection* gc, PurpleBuddy* buddy, PurpleGroup* group ); +void mxit_remove_buddy( PurpleConnection* gc, PurpleBuddy* buddy, PurpleGroup* group ); +void mxit_buddy_alias( PurpleConnection* gc, const char* who, const char* alias ); +void mxit_buddy_group( PurpleConnection* gc, const char* who, const char* old_group, const char* new_group ); +void mxit_rename_group( PurpleConnection* gc, const char* old_name, PurpleGroup* group, GList* moved_buddies ); + + +#endif /* _MXIT_ROSTER_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/splashscreen.c Mon Nov 09 19:27:45 2009 +0000 @@ -0,0 +1,223 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- splash screens -- + * + * Andrew Victor <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include <libgen.h> +#include <glib/gstdio.h> + +#include "purple.h" +#include "imgstore.h" + +#include "protocol.h" +#include "mxit.h" +#include "splashscreen.h" + + +/*------------------------------------------------------------------------ + * Return the ID of the current splash-screen. + * + * @param session The MXit session object + * @return The ID of the splash-screen (or NULL if no splash-screen) + */ +const char* splash_current(struct MXitSession* session) +{ + const char* splashId = purple_account_get_string(session->acc, MXIT_CONFIG_SPLASHID, NULL); + + purple_debug_info(MXIT_PLUGIN_ID, "Current splashId: '%s'\n", splashId); + + if ((splashId != NULL) && (*splashId != '\0')) + return splashId; + else + return NULL; +} + + +/*------------------------------------------------------------------------ + * Indicate if splash-screen popups are enabled. + * + * @param session The MXit session object + * @return TRUE if the popup is enabled. + */ +gboolean splash_popup_enabled(struct MXitSession* session) +{ + return purple_account_get_bool(session->acc, MXIT_CONFIG_SPLASHPOPUP, DEFAULT_SPLASH_POPUP); +} + + +/*------------------------------------------------------------------------ + * Return if the current splash-screen is clickable. + * + * @param session The MXit session object + * @return TRUE or FALSE + */ +static gboolean splash_clickable(struct MXitSession* session) +{ + return purple_account_get_bool(session->acc, MXIT_CONFIG_SPLASHCLICK, FALSE); +} + + +/*------------------------------------------------------------------------ + * Remove the stored splash-screen (if it exists). + * + * @param session The MXit session object + */ +void splash_remove(struct MXitSession* session) +{ + const char* splashId = NULL; + char* filename; + + /* Get current splash ID */ + splashId = splash_current(session); + + if (splashId != NULL) { + purple_debug_info(MXIT_PLUGIN_ID, "Removing splashId: '%s'\n", splashId); + + /* Delete stored splash image */ + filename = g_strdup_printf("%s/mxit/%s.png", purple_user_dir(), splashId); + g_unlink(filename); + g_free(filename); + + /* Clear current splash ID from settings */ + purple_account_set_string(session->acc, MXIT_CONFIG_SPLASHID, ""); + purple_account_set_bool(session->acc, MXIT_CONFIG_SPLASHCLICK, FALSE); + } +} + + +/*------------------------------------------------------------------------ + * Save a new splash-screen for later display. + * + * @param session The MXit session object + * @param splashID The ID of the splash-screen + * @param data Splash-screen image data (PNG format) + * @param datalen Splash-screen image data size + */ +void splash_update(struct MXitSession* session, const char* splashId, const char* data, int datalen, gboolean clickable) +{ + char* dir; + char* filename; + + /* Remove the current splash-screen */ + splash_remove(session); + + /* Save the new splash image */ + dir = g_strdup_printf("%s/mxit", purple_user_dir()); + purple_build_dir(dir, S_IRUSR | S_IWUSR | S_IXUSR); /* ensure directory exists */ + + filename = g_strdup_printf("%s/%s.png", dir, splashId); + if (purple_util_write_data_to_file_absolute(filename, data, datalen)) { + /* Store new splash-screen ID to settings */ + purple_account_set_string(session->acc, MXIT_CONFIG_SPLASHID, splashId); + purple_account_set_bool(session->acc, MXIT_CONFIG_SPLASHCLICK, clickable ); + } + + g_free(dir); + g_free(filename); +} + + +/*------------------------------------------------------------------------ + * The user has clicked OK on the Splash request form. + * + * @param gc The connection object + * @param fields The list of fields in the accepted form + */ +static void splash_click_ok(PurpleConnection* gc, PurpleRequestFields* fields) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + const char* splashId; + + /* Get current splash ID */ + splashId = splash_current(session); + if (!splashId) + return; + + /* if is clickable, then send click event */ + if (splash_clickable(session)) + mxit_send_splashclick(session, splashId); +} + + +/*------------------------------------------------------------------------ + * Display the current splash-screen. + * + * @param session The MXit session object + */ +void splash_display(struct MXitSession* session) +{ + const char* splashId = NULL; + char* filename; + gchar* imgdata; + gsize imglen; + int imgid = -1; + + /* Get current splash ID */ + splashId = splash_current(session); + if (splashId == NULL) /* no splash-screen */ + return; + + purple_debug_info(MXIT_PLUGIN_ID, "Display Splash: '%s'\n", splashId); + + /* Load splash-screen image from file */ + filename = g_strdup_printf("%s/mxit/%s.png", purple_user_dir(), splashId); + if (g_file_get_contents(filename, &imgdata, &imglen, NULL)) { + char buf[128]; + + /* Add splash-image to imagestore */ + imgid = purple_imgstore_add_with_id(g_memdup(imgdata, imglen), imglen, NULL); + + /* Generate and display message */ + g_snprintf(buf, sizeof(buf), "<img id=\"%d\">", imgid); + + /* Open a request-type popup to display the image */ + { + PurpleRequestFields* fields; + PurpleRequestFieldGroup* group; + PurpleRequestField* field; + + fields = purple_request_fields_new(); + group = purple_request_field_group_new(NULL); + purple_request_fields_add_group(fields, group); + + field = purple_request_field_image_new("splash", "", imgdata, imglen); /* add splash image */ + purple_request_field_group_add_field(group, field); + + if (splash_clickable(session)) { + purple_request_fields(session->con, _("MXit Advertising"), NULL, NULL, fields, + _("More Information"), G_CALLBACK(splash_click_ok), _("Close"), NULL, session->acc, NULL, NULL, session->con); + } + else { + purple_request_fields(session->con, _("MXit Advertising"), NULL, NULL, fields, + _("Continue"), G_CALLBACK(splash_click_ok), _("Close"), NULL, session->acc, NULL, NULL, session->con); + } + } + + /* Release reference to image */ + purple_imgstore_unref_by_id(imgid); + + g_free(imgdata); + } + + g_free(filename); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/splashscreen.h Mon Nov 09 19:27:45 2009 +0000 @@ -0,0 +1,59 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- splash screens -- + * + * Andrew Victor <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef _MXIT_SPLASHSCREEN_H_ +#define _MXIT_SPLASHSCREEN_H_ + +#define HANDLE_SPLASH1 "plas1.png" +#define HANDLE_SPLASH2 "plas2.png" + +#define DEFAULT_SPLASH_POPUP FALSE /* disabled by default */ + +/* + * Return the ID of the current splash-screen. + */ +const char* splash_current(struct MXitSession* session); + +/* + * Indicate if splash-screen popups are enabled. + */ +gboolean splash_popup_enabled(); + +/* + * Save a new splash-screen. + */ +void splash_update(struct MXitSession* session, const char* splashId, const char* data, int datalen, gboolean clickable); + +/* + * Remove the stored splash-screen (if it exists). + */ +void splash_remove(struct MXitSession* session); + +/* + * Display the current splash-screen. + */ +void splash_display(struct MXitSession* session); + +#endif /* _MXIT_SPLASHSCREEN_H_ */
--- a/libpurple/protocols/oscar/clientlogin.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/oscar/clientlogin.c Mon Nov 09 19:27:45 2009 +0000 @@ -40,6 +40,7 @@ #include "core.h" #include "oscar.h" +#include "oscarcommon.h" #define URL_CLIENT_LOGIN "https://api.screenname.aol.com/auth/clientLogin" #define URL_START_OSCAR_SESSION "http://api.oscar.aol.com/aim/startOSCARSession" @@ -102,11 +103,15 @@ return signature; } -static gboolean parse_start_oscar_session_response(PurpleConnection *gc, const gchar *response, gsize response_len, char **host, unsigned short *port, char **cookie) +static gboolean parse_start_oscar_session_response(PurpleConnection *gc, const gchar *response, gsize response_len, char **host, unsigned short *port, char **cookie, char **tls_certname) { xmlnode *response_node, *tmp_node, *data_node; - xmlnode *host_node = NULL, *port_node = NULL, *cookie_node = NULL; + xmlnode *host_node = NULL, *port_node = NULL, *cookie_node = NULL, *tls_node = NULL; + gboolean use_tls; char *tmp; + guint code; + + use_tls = purple_account_get_bool(purple_connection_get_account(gc), "use_ssl", OSCAR_DEFAULT_USE_SSL); /* Parse the response as XML */ response_node = xmlnode_from_str(response, response_len); @@ -131,6 +136,7 @@ host_node = xmlnode_get_child(data_node, "host"); port_node = xmlnode_get_child(data_node, "port"); cookie_node = xmlnode_get_child(data_node, "cookie"); + tls_node = xmlnode_get_child(data_node, "tlsCertName"); } /* Make sure we have a status code */ @@ -148,12 +154,13 @@ } /* Make sure the status code was 200 */ - if (strcmp(tmp, "200") != 0) + code = atoi(tmp); + if (code != 200) { purple_debug_error("oscar", "startOSCARSession response statusCode " "was %s: %s\n", tmp, response); - if (strcmp(tmp, "401") == 0) + if (code == 401 || code == 607) purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, _("You have been connecting and disconnecting too " @@ -177,7 +184,8 @@ /* Make sure we have everything else */ if (data_node == NULL || host_node == NULL || - port_node == NULL || cookie_node == NULL) + port_node == NULL || cookie_node == NULL || + (use_tls && tls_node == NULL)) { char *msg; purple_debug_error("oscar", "startOSCARSession response was missing " @@ -195,7 +203,12 @@ *host = xmlnode_get_data_unescaped(host_node); tmp = xmlnode_get_data_unescaped(port_node); *cookie = xmlnode_get_data_unescaped(cookie_node); - if (*host == NULL || **host == '\0' || tmp == NULL || *tmp == '\0' || cookie == NULL || *cookie == '\0') + + if (use_tls) + *tls_certname = xmlnode_get_data_unescaped(tls_node); + + if (*host == NULL || **host == '\0' || tmp == NULL || *tmp == '\0' || *cookie == NULL || **cookie == '\0' || + (use_tls && (*tls_certname == NULL || **tls_certname == '\0'))) { char *msg; purple_debug_error("oscar", "startOSCARSession response was missing " @@ -223,6 +236,7 @@ OscarData *od; PurpleConnection *gc; char *host, *cookie; + char *tls_certname = NULL; unsigned short port; guint8 *cookiedata; gsize cookiedata_len; @@ -244,28 +258,30 @@ return; } - if (!parse_start_oscar_session_response(gc, url_text, len, &host, &port, &cookie)) + if (!parse_start_oscar_session_response(gc, url_text, len, &host, &port, &cookie, &tls_certname)) return; cookiedata = purple_base64_decode(cookie, &cookiedata_len); - oscar_connect_to_bos(gc, od, host, port, cookiedata, cookiedata_len); + oscar_connect_to_bos(gc, od, host, port, cookiedata, cookiedata_len, tls_certname); g_free(cookiedata); g_free(host); g_free(cookie); + g_free(tls_certname); } static void send_start_oscar_session(OscarData *od, const char *token, const char *session_key, time_t hosttime) { char *query_string, *signature, *url; + gboolean use_tls = purple_account_get_bool(purple_connection_get_account(od->gc), "use_ssl", OSCAR_DEFAULT_USE_SSL); /* Construct the GET parameters */ query_string = g_strdup_printf("a=%s" "&f=xml" "&k=%s" "&ts=%" PURPLE_TIME_T_MODIFIER - "&useTLS=0", - purple_url_encode(token), get_client_key(od), hosttime); + "&useTLS=%d", + purple_url_encode(token), get_client_key(od), hosttime, use_tls); signature = generate_signature("GET", URL_START_OSCAR_SESSION, query_string, session_key); url = g_strdup_printf(URL_START_OSCAR_SESSION "?%s&sig_sha256=%s",
--- a/libpurple/protocols/oscar/family_feedbag.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/oscar/family_feedbag.c Mon Nov 09 19:27:45 2009 +0000 @@ -389,11 +389,10 @@ /** * Locally find the presence flag item, and return the setting. The returned setting is a - * bitmask of the user flags that you are visible to. See the AIM_FLAG_* #defines - * in oscar.h + * bitmask of the preferences. See the AIM_SSI_PRESENCE_FLAG_* #defines in oscar.h. * * @param list A pointer to the current list of items. - * @return Return the current visibility mask. + * @return Return the current set of preferences. */ guint32 aim_ssi_getpresence(struct aim_ssi_item *list) { @@ -1130,9 +1129,11 @@ * should show up as idle or not, etc. * * @param od The oscar odion. - * @param presence I think it's a bitmask, but I only know what one of the bits is: - * 0x00000002 - Hide wireless? + * @param presence A bitmask of the first 32 entries [0-31] from + * http://dev.aol.com/aim/oscar/#FEEDBAG__BUDDY_PREFS + * 0x00000002 - Hide "eBuddy group" (whatever that is) * 0x00000400 - Allow others to see your idle time + * 0x00020000 - Don't show Recent Buddies * @return Return 0 if no errors, otherwise return the error number. */ int aim_ssi_setpresence(OscarData *od, guint32 presence) {
--- a/libpurple/protocols/oscar/family_icbm.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/oscar/family_icbm.c Mon Nov 09 19:27:45 2009 +0000 @@ -151,6 +151,55 @@ return AIM_CLIENTTYPE_UNKNOWN; } +/* + * Subtype 0x0001 - Error + */ +static int +error(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs) +{ + int ret = 0; + aim_rxcallback_t userfunc; + aim_snac_t *snac2; + guint16 reason, errcode = 0; + char *bn; + GSList *tlvlist; + + if (!(snac2 = aim_remsnac(od, snac->id))) { + purple_debug_misc("oscar", "icbm error: received response from unknown request!\n"); + return 0; + } + + if (snac2->family != SNAC_FAMILY_ICBM) { + purple_debug_misc("oscar", "icbm error: received response from invalid request! %d\n", snac2->family); + g_free(snac2->data); + g_free(snac2); + return 0; + } + + if (!(bn = snac2->data)) { + purple_debug_misc("oscar", "icbm error: received response from request without a buddy name!\n"); + g_free(snac2); + return 0; + } + + reason = byte_stream_get16(bs); + + tlvlist = aim_tlvlist_read(bs); + if (aim_tlv_gettlv(tlvlist, 0x0008, 1)) + errcode = aim_tlv_get16(tlvlist, 0x0008, 1); + aim_tlvlist_free(tlvlist); + + /* Notify the user that the message wasn't delivered */ + if ((userfunc = aim_callhandler(od, snac->family, snac->subtype))) + ret = userfunc(od, conn, frame, reason, errcode, bn); + + if (snac2) + g_free(snac2->data); + g_free(snac2); + + return ret; +} + /** * Subtype 0x0002 - Set ICBM parameters. * @@ -2789,7 +2838,9 @@ static int snachandler(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs) { - if (snac->subtype == 0x0005) + if (snac->subtype == 0x0001) + return error(od, conn, mod, frame, snac, bs); + else if (snac->subtype == 0x0005) return aim_im_paraminfo(od, conn, mod, frame, snac, bs); else if (snac->subtype == 0x0006) return outgoingim(od, conn, mod, frame, snac, bs);
--- a/libpurple/protocols/oscar/family_oservice.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/oscar/family_oservice.c Mon Nov 09 19:27:45 2009 +0000 @@ -319,7 +319,10 @@ for (i = 0; i < numclasses; i++) { struct rateclass *rateclass; + guint32 delta; + struct timeval now; + gettimeofday(&now, NULL); rateclass = g_new0(struct rateclass, 1); rateclass->classid = byte_stream_get16(bs); @@ -339,11 +342,24 @@ * the new version hardcoded here. */ if (mod->version >= 3) - byte_stream_getrawbuf(bs, rateclass->unknown, sizeof(rateclass->unknown)); + { + rateclass->delta = byte_stream_get32(bs); + rateclass->dropping_snacs = byte_stream_get8(bs); + + delta = rateclass->delta; + + rateclass->last.tv_sec = now.tv_sec - delta / 1000; + delta %= 1000; + rateclass->last.tv_usec = now.tv_usec - delta * 1000; + } + else + { + rateclass->delta = rateclass->dropping_snacs = 0; + rateclass->last.tv_sec = now.tv_sec; + rateclass->last.tv_usec = now.tv_usec; + } rateclass->members = g_hash_table_new(g_direct_hash, g_direct_equal); - rateclass->last.tv_sec = 0; - rateclass->last.tv_usec = 0; conn->rateclasses = g_slist_prepend(conn->rateclasses, rateclass); } conn->rateclasses = g_slist_reverse(conn->rateclasses); @@ -383,8 +399,7 @@ */ /* - * Last step in the conn init procedure is to acknowledge that we - * agree to these draconian limitations. + * Subscribe to rate change information for all rate classes. */ aim_srv_rates_addparam(od, conn); @@ -451,7 +466,10 @@ aim_rxcallback_t userfunc; guint16 code, classid; struct rateclass *rateclass; + guint32 delta; + struct timeval now; + gettimeofday(&now, NULL); code = byte_stream_get16(bs); classid = byte_stream_get16(bs); @@ -468,8 +486,29 @@ rateclass->current = byte_stream_get32(bs); rateclass->max = byte_stream_get32(bs); - if ((userfunc = aim_callhandler(od, snac->family, snac->subtype))) - ret = userfunc(od, conn, frame, code, classid, rateclass->windowsize, rateclass->clear, rateclass->alert, rateclass->limit, rateclass->disconnect, rateclass->current, rateclass->max); + if (mod->version >= 3) + { + rateclass->delta = byte_stream_get32(bs); + rateclass->dropping_snacs = byte_stream_get8(bs); + + delta = rateclass->delta; + + rateclass->last.tv_sec = now.tv_sec - delta / 1000; + delta %= 1000; + rateclass->last.tv_usec = now.tv_usec - delta * 1000; + } + else + { + rateclass->delta = rateclass->dropping_snacs = 0; + rateclass->last.tv_sec = now.tv_sec; + rateclass->last.tv_usec = now.tv_usec; + } + + if ((userfunc = aim_callhandler(od, snac->family, snac->subtype))) { + /* Can't pass in guint8 via ... varargs, so we use an unsigned int */ + unsigned int dropping_snacs = rateclass->dropping_snacs; + ret = userfunc(od, conn, frame, code, classid, rateclass->windowsize, rateclass->clear, rateclass->alert, rateclass->limit, rateclass->disconnect, rateclass->current, rateclass->max, rateclass->delta, dropping_snacs); + } return ret; }
--- a/libpurple/protocols/oscar/flap_connection.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/oscar/flap_connection.c Mon Nov 09 19:27:45 2009 +0000 @@ -73,7 +73,7 @@ } void -flap_connection_send_version_with_cookie_and_clientinfo(OscarData *od, FlapConnection *conn, guint16 length, const guint8 *chipsahoy, ClientInfo *ci) +flap_connection_send_version_with_cookie_and_clientinfo(OscarData *od, FlapConnection *conn, guint16 length, const guint8 *chipsahoy, ClientInfo *ci, gboolean allow_multiple_logins) { FlapFrame *frame; GSList *tlvlist = NULL; @@ -94,7 +94,7 @@ aim_tlvlist_add_16(&tlvlist, 0x0018, (guint16)ci->minor); aim_tlvlist_add_16(&tlvlist, 0x0019, (guint16)ci->point); aim_tlvlist_add_16(&tlvlist, 0x001a, (guint16)ci->build); - aim_tlvlist_add_8(&tlvlist, 0x004a, 0x01); + aim_tlvlist_add_8(&tlvlist, 0x004a, (allow_multiple_logins ? 0x01 : 0x03)); aim_tlvlist_write(&frame->data, &tlvlist); @@ -131,11 +131,13 @@ rateclass_get_new_current(FlapConnection *conn, struct rateclass *rateclass, struct timeval *now) { unsigned long timediff; /* In milliseconds */ + guint32 current; timediff = (now->tv_sec - rateclass->last.tv_sec) * 1000 + (now->tv_usec - rateclass->last.tv_usec) / 1000; + current = ((rateclass->current * (rateclass->windowsize - 1)) + timediff) / rateclass->windowsize; - /* This formula is taken from the joscar API docs. Preesh. */ - return MIN(((rateclass->current * (rateclass->windowsize - 1)) + timediff) / rateclass->windowsize, rateclass->max); + /* This formula is taken from http://dev.aol.com/aim/oscar/#RATELIMIT */ + return MIN(current, rateclass->max); } /* @@ -161,8 +163,7 @@ new_current = rateclass_get_new_current(conn, rateclass, &now); - /* (Add 100ms padding to account for inaccuracies in the calculation) */ - if (new_current < rateclass->alert + 100) + if (rateclass->dropping_snacs || new_current <= rateclass->alert) /* Not ready to send this SNAC yet--keep waiting. */ return FALSE; @@ -245,10 +246,9 @@ gettimeofday(&now, NULL); new_current = rateclass_get_new_current(conn, rateclass, &now); - /* (Add 100ms padding to account for inaccuracies in the calculation) */ - if (new_current < rateclass->alert + 100) + if (rateclass->dropping_snacs || new_current <= rateclass->alert) { - purple_debug_info("oscar", "Current rate for conn %p would be %u, but we alert at %u; enqueueing\n", conn, new_current, (rateclass->alert + 100)); + purple_debug_info("oscar", "Current rate for conn %p would be %u, but we alert at %u; enqueueing\n", conn, new_current, rateclass->alert); enqueue = TRUE; }
--- a/libpurple/protocols/oscar/libaim.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/oscar/libaim.c Mon Nov 09 19:27:45 2009 +0000 @@ -141,7 +141,7 @@ static void init_plugin(PurplePlugin *plugin) { - oscar_init(PURPLE_PLUGIN_PROTOCOL_INFO(plugin)); + oscar_init(plugin); } PURPLE_INIT_PLUGIN(aim, init_plugin, info);
--- a/libpurple/protocols/oscar/libicq.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/oscar/libicq.c Mon Nov 09 19:27:45 2009 +0000 @@ -153,7 +153,7 @@ { PurpleAccountOption *option; - oscar_init(PURPLE_PLUGIN_PROTOCOL_INFO(plugin)); + oscar_init(plugin); option = purple_account_option_string_new(_("Encoding"), "encoding", OSCAR_DEFAULT_CUSTOM_ENCODING); prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
--- a/libpurple/protocols/oscar/oscar.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/oscar/oscar.c Mon Nov 09 19:27:45 2009 +0000 @@ -144,6 +144,26 @@ }; static const int msgerrreasonlen = G_N_ELEMENTS(msgerrreason); +static const char * const errcodereason[] = { + N_("Invalid error"), + N_("Not logged in"), + N_("Cannot receive IM due to parental controls"), + N_("Cannot send SMS without accepting terms"), + N_("Cannot send SMS"), /* SMS_WITHOUT_DISCLAIMER is weird */ + N_("Cannot send SMS to this country"), + N_("Unknown error"), /* Undocumented */ + N_("Unknown error"), /* Undocumented */ + N_("Cannot send SMS to unknown country"), + N_("Bot accounts cannot initiate IMs"), + N_("Bot account cannot IM this user"), + N_("Bot account reached IM limit"), + N_("Bot account reached daily IM limit"), + N_("Bot account reached monthly IM limit"), + N_("Unable to receive offline messages"), + N_("Offline message store full") +}; +static const int errcodereasonlen = G_N_ELEMENTS(errcodereason); + /* All the libfaim->purple callback functions */ /* Only used when connecting with the old-style BUCP login */ @@ -1168,7 +1188,8 @@ ClientInfo icqinfo = CLIENTINFO_PURPLE_ICQ; flap_connection_send_version_with_cookie_and_clientinfo(od, conn, conn->cookielen, conn->cookie, - od->icq ? &icqinfo : &aiminfo); + od->icq ? &icqinfo : &aiminfo, + purple_account_get_bool(account, "allow_multiple_logins", OSCAR_DEFAULT_ALLOW_MULTIPLE_LOGINS)); } else { flap_connection_send_version_with_cookie(od, conn, conn->cookielen, conn->cookie); @@ -1394,9 +1415,9 @@ presence = aim_ssi_getpresence(od->ssi.local); if (report_idle) - aim_ssi_setpresence(od, presence | 0x400); + aim_ssi_setpresence(od, presence | AIM_SSI_PRESENCE_FLAG_SHOWIDLE); else - aim_ssi_setpresence(od, presence & ~0x400); + aim_ssi_setpresence(od, presence & ~AIM_SSI_PRESENCE_FLAG_SHOWIDLE); } /** @@ -1806,17 +1827,35 @@ return 1; } -int oscar_connect_to_bos(PurpleConnection *gc, OscarData *od, const char *host, guint16 port, guint8 *cookie, guint16 cookielen) +int oscar_connect_to_bos(PurpleConnection *gc, OscarData *od, const char *host, guint16 port, guint8 *cookie, guint16 cookielen, const char *tls_certname) { + PurpleAccount *account; FlapConnection *conn; + account = purple_connection_get_account(gc); + conn = flap_connection_new(od, SNAC_FAMILY_LOCATE); conn->cookielen = cookielen; conn->cookie = g_memdup(cookie, cookielen); - conn->connect_data = purple_proxy_connect(NULL, - purple_connection_get_account(gc), host, port, - connection_established_cb, conn); - if (conn->connect_data == NULL) + + /* + * tls_certname is only set (and must be set if we get this far) if + * SSL is enabled. + */ + if (tls_certname) + { + conn->gsc = purple_ssl_connect_with_ssl_cn(account, host, port, + ssl_connection_established_cb, ssl_connection_error_cb, + tls_certname, conn); + } + else + { + conn->connect_data = purple_proxy_connect(NULL, + account, host, port, + connection_established_cb, conn); + } + + if (conn->gsc == NULL && conn->connect_data == NULL) { purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Unable to connect")); return 0; @@ -1877,7 +1916,7 @@ break; case 0x18: /* username connecting too frequently */ - purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, _("You have been connecting and disconnecting too frequently. Wait ten minutes and try again. If you continue to try, you will need to wait even longer.")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, _("Your username has been connecting and disconnecting too frequently. Wait ten minutes and try again. If you continue to try, you will need to wait even longer.")); break; case 0x1c: { @@ -1889,7 +1928,7 @@ } case 0x1d: /* IP address connecting too frequently */ - purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, _("You have been connecting and disconnecting too frequently. Wait a minute and try again. If you continue to try, you will need to wait even longer.")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, _("Your IP address has been connecting and disconnecting too frequently. Wait a minute and try again. If you continue to try, you will need to wait even longer.")); break; default: purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("Unknown reason")); @@ -2874,25 +2913,46 @@ gchar **text; text = g_strsplit(args->msg, "\376", 0); if (text) { - num = 0; - for (i=0; i<strlen(text[0]); i++) - num = num*10 + text[0][i]-48; - for (i=0; i<num; i++) { - struct name_data *data = g_new(struct name_data, 1); - gchar *message = g_strdup_printf(_("ICQ user %u has sent you a buddy: %s (%s)"), args->uin, text[i*2+2], text[i*2+1]); - data->gc = gc; - data->name = g_strdup(text[i*2+1]); - data->nick = g_strdup(text[i*2+2]); - - purple_request_action(gc, NULL, message, - _("Do you want to add this buddy " - "to your buddy list?"), - PURPLE_DEFAULT_ACTION_NONE, - purple_connection_get_account(gc), data->name, NULL, - data, 2, - _("_Add"), G_CALLBACK(purple_icq_buddyadd), - _("_Decline"), G_CALLBACK(oscar_free_name_data)); - g_free(message); + /* Read the number of contacts that we were sent */ + errno = 0; + num = text[0] ? strtoul(text[0], NULL, 10) : 0; + + if (num > 0 && errno == 0) { + for (i=0; i<num; i++) { + struct name_data *data; + gchar *message; + + if (!text[i*2 + 1] || !text[i*2 + 2]) { + /* We're missing the contact name or nickname. Bail out. */ + gchar *tmp = g_strescape(args->msg, NULL); + purple_debug_error("oscar", "Unknown syntax parsing " + "ICQ buddies. args->msg=%s\n", tmp); + g_free(tmp); + break; + } + + message = g_strdup_printf(_("ICQ user %u has sent you a buddy: %s (%s)"), args->uin, text[i*2+2], text[i*2+1]); + + data = g_new(struct name_data, 1); + data->gc = gc; + data->name = g_strdup(text[i*2+1]); + data->nick = g_strdup(text[i*2+2]); + + purple_request_action(gc, NULL, message, + _("Do you want to add this buddy " + "to your buddy list?"), + PURPLE_DEFAULT_ACTION_NONE, + purple_connection_get_account(gc), data->name, NULL, + data, 2, + _("_Add"), G_CALLBACK(purple_icq_buddyadd), + _("_Decline"), G_CALLBACK(oscar_free_name_data)); + g_free(message); + } + } else { + gchar *tmp = g_strescape(args->msg, NULL); + purple_debug_error("oscar", "Unknown syntax parsing " + "ICQ buddies. args->msg=%s\n", tmp); + g_free(tmp); } g_strfreev(text); } @@ -3196,17 +3256,18 @@ PurpleXfer *xfer; #endif va_list ap; - guint16 reason; - char *data, *buf; + guint16 reason, errcode; + char *data, *reason_str, *buf; va_start(ap, fr); reason = (guint16)va_arg(ap, unsigned int); + errcode = (guint16)va_arg(ap, unsigned int); data = va_arg(ap, char *); va_end(ap); purple_debug_error("oscar", - "Message error with data %s and reason %hu\n", - (data != NULL ? data : ""), reason); + "Message error with data %s and reason %hu and errcode %hu\n", + (data != NULL ? data : ""), reason, errcode); if ((data == NULL) || (*data == '\0')) /* We can't do anything if data is empty */ @@ -3221,14 +3282,27 @@ #endif /* Data is assumed to be the destination bn */ - buf = g_strdup_printf(_("Unable to send message: %s"), (reason < msgerrreasonlen) ? _(msgerrreason[reason]) : _("Unknown reason.")); + + reason_str = g_strdup((reason < msgerrreasonlen) ? _(msgerrreason[reason]) : _("Unknown reason")); + if (errcode != 0 && errcode < errcodereasonlen) + buf = g_strdup_printf(_("Unable to send message: %s (%s)"), reason_str, + _(errcodereason[errcode])); + else + buf = g_strdup_printf(_("Unable to send message: %s"), reason_str); + if (!purple_conv_present_error(data, purple_connection_get_account(gc), buf)) { g_free(buf); - buf = g_strdup_printf(_("Unable to send message to %s:"), data ? data : "(unknown)"); - purple_notify_error(od->gc, NULL, buf, - (reason < msgerrreasonlen) ? _(msgerrreason[reason]) : _("Unknown reason.")); + if (errcode != 0 && errcode < errcodereasonlen) + buf = g_strdup_printf(_("Unable to send message to %s: %s (%s)"), + data ? data : "(unknown)", reason_str, + _(errcodereason[errcode])); + else + buf = g_strdup_printf(_("Unable to send message to %s: %s"), + data ? data : "(unknown)", reason_str); + purple_notify_error(od->gc, NULL, buf, reason_str); } g_free(buf); + g_free(reason_str); return 1; } @@ -3719,7 +3793,8 @@ }; va_list ap; guint16 code, rateclass; - guint32 windowsize, clear, alert, limit, disconnect, currentavg, maxavg; + guint32 windowsize, clear, alert, limit, disconnect, currentavg, maxavg, delta; + guint8 dropping_snacs; va_start(ap, fr); code = (guint16)va_arg(ap, unsigned int); @@ -3731,23 +3806,28 @@ disconnect = va_arg(ap, guint32); currentavg = va_arg(ap, guint32); maxavg = va_arg(ap, guint32); + delta = va_arg(ap, guint32); + dropping_snacs = (guint8)va_arg(ap, unsigned int); va_end(ap); purple_debug_misc("oscar", "rate %s (param ID 0x%04hx): curavg = %u, maxavg = %u, alert at %u, " - "clear warning at %u, limit at %u, disconnect at %u (window size = %u)\n", + "clear warning at %u, limit at %u, disconnect at %u, delta is %u, dropping is %u (window size = %u)\n", (code < 5) ? codes[code] : codes[0], rateclass, currentavg, maxavg, alert, clear, limit, disconnect, - windowsize); + delta, + dropping_snacs, + windowsize + ); if (code == AIM_RATE_CODE_LIMIT) { purple_debug_warning("oscar", _("The last action you attempted could not be " "performed because you are over the rate limit. " - "Please wait 10 seconds and try again.")); + "Please wait 10 seconds and try again.\n")); } return 1; @@ -3909,12 +3989,8 @@ od->rights.maxpermits = (guint)maxpermits; od->rights.maxdenies = (guint)maxdenies; - purple_connection_set_state(gc, PURPLE_CONNECTED); - purple_debug_info("oscar", "buddy list loaded\n"); - aim_srv_clientready(od, conn); - if (purple_account_get_user_info(account) != NULL) serv_set_info(gc, purple_account_get_user_info(account)); @@ -3941,9 +4017,6 @@ presence = purple_status_get_presence(status); aim_srv_setidle(od, !purple_presence_is_idle(presence) ? 0 : time(NULL) - purple_presence_get_idle_time(presence)); - /* Request offline messages for AIM and ICQ */ - aim_im_reqofflinemsgs(od); - if (od->icq) { #ifdef OLDSTYLE_ICQ_OFFLINEMSGS aim_icq_reqofflinemsgs(od); @@ -3957,6 +4030,26 @@ aim_srv_requestnew(od, SNAC_FAMILY_ALERT); aim_srv_requestnew(od, SNAC_FAMILY_CHATNAV); + od->bos.have_rights = TRUE; + + /* + * If we've already received our feedbag data then we're not waiting on + * anything else, so send the server clientready. + * + * Normally we get bos rights before we get our feedbag data, so this + * rarely (never?) happens. And I'm not sure it actually matters if we + * wait for bos rights before calling clientready. But it seems safer + * to do it this way. + */ + if (od->ssi.received_data) { + aim_srv_clientready(od, conn); + + /* Request offline messages for AIM and ICQ */ + aim_im_reqofflinemsgs(od); + + purple_connection_set_state(gc, PURPLE_CONNECTED); + } + return 1; } @@ -5151,7 +5244,7 @@ { /* If not in server list then prune from local list */ GSList *cur, *next; GSList *buddies = purple_find_buddies(account, NULL); - + /* Buddies */ cur = NULL; @@ -5231,9 +5324,9 @@ report_idle = strcmp(idle_reporting_pref, "none") != 0; if (report_idle) - aim_ssi_setpresence(od, tmp | 0x400); + aim_ssi_setpresence(od, tmp | AIM_SSI_PRESENCE_FLAG_SHOWIDLE); else - aim_ssi_setpresence(od, tmp & ~0x400); + aim_ssi_setpresence(od, tmp & ~AIM_SSI_PRESENCE_FLAG_SHOWIDLE); } @@ -5241,45 +5334,28 @@ /* Add from server list to local list */ for (curitem=od->ssi.local; curitem; curitem=curitem->next) { - if ((curitem->name == NULL) || (g_utf8_validate(curitem->name, -1, NULL))) switch (curitem->type) { - case 0x0000: { /* Buddy */ + case AIM_SSI_TYPE_BUDDY: { /* Buddy */ if (curitem->name) { struct aim_ssi_item *groupitem; - char *gname, *gname_utf8, *alias, *alias_utf8; + const char *gname, *alias; groupitem = aim_ssi_itemlist_find(od->ssi.local, curitem->gid, 0x0000); gname = groupitem ? groupitem->name : NULL; - if (gname != NULL) { - if (g_utf8_validate(gname, -1, NULL)) - gname_utf8 = g_strdup(gname); - else - gname_utf8 = oscar_utf8_try_convert(account, gname); - } else - gname_utf8 = NULL; - - g = purple_find_group(gname_utf8 ? gname_utf8 : _("Orphans")); + + g = purple_find_group(gname ? gname : _("Orphans")); if (g == NULL) { - g = purple_group_new(gname_utf8 ? gname_utf8 : _("Orphans")); + g = purple_group_new(gname ? gname : _("Orphans")); purple_blist_add_group(g, NULL); } alias = aim_ssi_getalias(od->ssi.local, gname, curitem->name); - if (alias != NULL) { - if (g_utf8_validate(alias, -1, NULL)) - alias_utf8 = g_strdup(alias); - else - alias_utf8 = oscar_utf8_try_convert(account, alias); - g_free(alias); - } else - alias_utf8 = NULL; - b = purple_find_buddy_in_group(account, curitem->name, g); if (b) { /* Get server stored alias */ - purple_blist_alias_buddy(b, alias_utf8); + purple_blist_alias_buddy(b, alias); } else { - b = purple_buddy_new(account, curitem->name, alias_utf8); + b = purple_buddy_new(account, curitem->name, alias); purple_debug_info("oscar", "ssi: adding buddy %s to group %s to local list\n", curitem->name, gname); @@ -5303,33 +5379,18 @@ purple_buddy_get_name(b), OSCAR_STATUS_ID_MOBILE, NULL); } - - g_free(gname_utf8); - g_free(alias_utf8); } } break; - case 0x0001: { /* Group */ - char *gname; - char *gname_utf8; - - gname = curitem->name; - if (gname != NULL) { - if (g_utf8_validate(gname, -1, NULL)) - gname_utf8 = g_strdup(gname); - else - gname_utf8 = oscar_utf8_try_convert(account, gname); - } else - gname_utf8 = NULL; - - if (gname_utf8 != NULL && purple_find_group(gname_utf8) == NULL) { - g = purple_group_new(gname_utf8); + case AIM_SSI_TYPE_GROUP: { /* Group */ + const char *gname = curitem->name; + if (gname != NULL && purple_find_group(gname) == NULL) { + g = purple_group_new(gname); purple_blist_add_group(g, NULL); } - g_free(gname_utf8); } break; - case 0x0002: { /* Permit buddy */ + case AIM_SSI_TYPE_PERMIT: { /* Permit buddy */ if (curitem->name) { /* if (!find_permdeny_by_name(gc->permit, curitem->name)) { AAA */ GSList *list; @@ -5342,7 +5403,7 @@ } } break; - case 0x0003: { /* Deny buddy */ + case AIM_SSI_TYPE_DENY: { /* Deny buddy */ if (curitem->name) { GSList *list; for (list=account->deny; (list && oscar_util_name_compare(curitem->name, list->data)); list=list->next); @@ -5354,7 +5415,7 @@ } } break; - case 0x0004: { /* Permit/deny setting */ + case AIM_SSI_TYPE_PDINFO: { /* Permit/deny setting */ /* * We don't inherit the permit/deny setting from the server * for ICQ because, for ICQ, this setting controls who can @@ -5372,7 +5433,7 @@ } } break; - case 0x0005: { /* Presence setting */ + case AIM_SSI_TYPE_PRESENCEPREFS: { /* Presence setting */ /* We don't want to change Purple's setting because it applies to all accounts */ } break; } /* End of switch on curitem->type */ @@ -5396,6 +5457,19 @@ oscar_set_icon(gc, img); purple_imgstore_unref(img); + /* + * If we've already received our bos rights then we're not waiting on + * anything else, so send the server clientready. + */ + if (od->bos.have_rights) { + aim_srv_clientready(od, conn); + + /* Request offline messages for AIM and ICQ */ + aim_im_reqofflinemsgs(od); + + purple_connection_set_state(gc, PURPLE_CONNECTED); + } + return 1; } @@ -5452,7 +5526,8 @@ { PurpleConnection *gc; PurpleAccount *account; - char *gname, *gname_utf8, *alias, *alias_utf8; + const char *gname; + char *alias; PurpleBuddy *b; PurpleGroup *g; struct aim_ssi_item *ssi_item; @@ -5473,19 +5548,7 @@ return 1; gname = aim_ssi_itemlist_findparentname(od->ssi.local, name); - gname_utf8 = gname ? oscar_utf8_try_convert(account, gname) : NULL; - alias = aim_ssi_getalias(od->ssi.local, gname, name); - if (alias != NULL) - { - if (g_utf8_validate(alias, -1, NULL)) - alias_utf8 = g_strdup(alias); - else - alias_utf8 = oscar_utf8_try_convert(account, alias); - } - else - alias_utf8 = NULL; - g_free(alias); b = purple_find_buddy(account, name); if (b) { @@ -5494,21 +5557,21 @@ * of your buddies, so update our local buddy list with * the person's new alias. */ - purple_blist_alias_buddy(b, alias_utf8); + purple_blist_alias_buddy(b, alias); } else if (snac_subtype == 0x0008) { /* * You're logged in somewhere else and you added a buddy to * your server list, so add them to your local buddy list. */ - b = purple_buddy_new(account, name, alias_utf8); - - if (!(g = purple_find_group(gname_utf8 ? gname_utf8 : _("Orphans")))) { - g = purple_group_new(gname_utf8 ? gname_utf8 : _("Orphans")); + b = purple_buddy_new(account, name, alias); + + if (!(g = purple_find_group(gname ? gname : _("Orphans")))) { + g = purple_group_new(gname ? gname : _("Orphans")); purple_blist_add_group(g, NULL); } purple_debug_info("oscar", - "ssi: adding buddy %s to group %s to local list\n", name, gname_utf8 ? gname_utf8 : _("Orphans")); + "ssi: adding buddy %s to group %s to local list\n", name, gname ? gname : _("Orphans")); purple_blist_add_buddy(b, NULL, g, NULL); /* Mobile users should always be online */ @@ -5521,6 +5584,8 @@ } + g_free(alias); + ssi_item = aim_ssi_itemlist_finditem(od->ssi.local, gname, name, AIM_SSI_TYPE_BUDDY); if (ssi_item == NULL) @@ -5530,9 +5595,6 @@ "group %s\n", name, gname); } - g_free(gname_utf8); - g_free(alias_utf8); - return 1; } @@ -6235,7 +6297,6 @@ struct name_data *data; PurpleGroup *g; char *comment; - gchar *comment_utf8; gchar *title; PurpleAccount *account; const char *name; @@ -6254,7 +6315,6 @@ data = g_new(struct name_data, 1); comment = aim_ssi_getcomment(od->ssi.local, purple_group_get_name(g), name); - comment_utf8 = comment ? oscar_utf8_try_convert(account, comment) : NULL; data->gc = gc; data->name = g_strdup(name); @@ -6262,7 +6322,7 @@ title = g_strdup_printf(_("Buddy Comment for %s"), data->name); purple_request_input(gc, title, _("Buddy Comment:"), NULL, - comment_utf8, TRUE, FALSE, NULL, + comment, TRUE, FALSE, NULL, _("_OK"), G_CALLBACK(oscar_ssi_editcomment), _("_Cancel"), G_CALLBACK(oscar_free_name_data), account, data->name, NULL, @@ -6270,7 +6330,6 @@ g_free(title); g_free(comment); - g_free(comment_utf8); } static void @@ -7076,8 +7135,9 @@ return FALSE; } -void oscar_init(PurplePluginProtocolInfo *prpl_info) +void oscar_init(PurplePlugin *plugin) { + PurplePluginProtocolInfo *prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin); PurpleAccountOption *option; static gboolean init = FALSE; @@ -7100,9 +7160,11 @@ OSCAR_DEFAULT_ALWAYS_USE_RV_PROXY); prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); - option = purple_account_option_bool_new(_("Allow multiple simultaneous logins"), "allow_multiple_logins", - OSCAR_DEFAULT_ALLOW_MULTIPLE_LOGINS); - prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); + if (g_str_equal(purple_plugin_get_id(plugin), "prpl-aim")) { + option = purple_account_option_bool_new(_("Allow multiple simultaneous logins"), "allow_multiple_logins", + OSCAR_DEFAULT_ALLOW_MULTIPLE_LOGINS); + prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); + } if (init) return;
--- a/libpurple/protocols/oscar/oscar.h Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/oscar/oscar.h Mon Nov 09 19:27:45 2009 +0000 @@ -535,6 +535,10 @@ struct aim_userinfo_s *userinfo; } locate; + struct { + gboolean have_rights; + } bos; + /* Server-stored information (ssi) */ struct { gboolean received_data; @@ -619,7 +623,7 @@ } chat; }; -int oscar_connect_to_bos(PurpleConnection *gc, OscarData *od, const char *host, guint16 port, guint8 *cookie, guint16 cookielen); +int oscar_connect_to_bos(PurpleConnection *gc, OscarData *od, const char *host, guint16 port, guint8 *cookie, guint16 cookielen, const char *tls_certname); /* family_auth.c */ @@ -657,7 +661,7 @@ void flap_connection_send(FlapConnection *conn, FlapFrame *frame); void flap_connection_send_version(OscarData *od, FlapConnection *conn); void flap_connection_send_version_with_cookie(OscarData *od, FlapConnection *conn, guint16 length, const guint8 *chipsahoy); -void flap_connection_send_version_with_cookie_and_clientinfo(OscarData *od, FlapConnection *conn, guint16 length, const guint8 *chipsahoy, ClientInfo *ci); +void flap_connection_send_version_with_cookie_and_clientinfo(OscarData *od, FlapConnection *conn, guint16 length, const guint8 *chipsahoy, ClientInfo *ci, gboolean allow_multiple_login); void flap_connection_send_snac(OscarData *od, FlapConnection *conn, guint16 family, const guint16 subtype, guint16 flags, aim_snacid_t snacid, ByteStream *data); void flap_connection_send_snac_with_priority(OscarData *od, FlapConnection *conn, guint16 family, const guint16 subtype, guint16 flags, aim_snacid_t snacid, ByteStream *data, gboolean high_priority); void flap_connection_send_keepalive(OscarData *od, FlapConnection *conn); @@ -1236,7 +1240,7 @@ #define AIM_SSI_ACK_INVALIDNAME 0x000d #define AIM_SSI_ACK_AUTHREQUIRED 0x000e -/* These flags are set in the 0x00c9 TLV of SSI teyp 0x0005 */ +/* These flags are set in the 0x00c9 TLV of SSI type 0x0005 */ #define AIM_SSI_PRESENCE_FLAG_SHOWIDLE 0x00000400 #define AIM_SSI_PRESENCE_FLAG_NORECENTBUDDIES 0x00020000 @@ -1681,7 +1685,8 @@ guint32 disconnect; guint32 current; guint32 max; - guint8 unknown[5]; /* only present in versions >= 3 */ + guint32 delta; + guint8 dropping_snacs; GHashTable *members; /* Key is family and subtype, value is TRUE. */ struct timeval last; /**< The time when we last sent a SNAC of this rate class. */
--- a/libpurple/protocols/oscar/oscarcommon.h Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/oscar/oscarcommon.h Mon Nov 09 19:27:45 2009 +0000 @@ -94,4 +94,4 @@ gboolean oscar_offline_message(const PurpleBuddy *buddy); void oscar_format_username(PurpleConnection *gc, const char *nick); GList *oscar_actions(PurplePlugin *plugin, gpointer context); -void oscar_init(PurplePluginProtocolInfo *prpl_info); +void oscar_init(PurplePlugin *plugin);
--- a/libpurple/protocols/silc/Makefile.mingw Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/silc/Makefile.mingw Mon Nov 09 19:27:45 2009 +0000 @@ -7,6 +7,8 @@ PIDGIN_TREE_TOP := ../../.. include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak +DEFINES := $(subst -DWIN32_LEAN_AND_MEAN,,$(DEFINES)) + TARGET = libsilc NEEDED_DLLS = $(SILC_TOOLKIT)/bin/libsilc-1-1-2.dll \ $(SILC_TOOLKIT)/bin/libsilcclient-1-1-2.dll @@ -79,7 +81,7 @@ $(OBJECTS): $(PURPLE_CONFIG_H) $(TARGET).dll: $(PURPLE_DLL).a $(OBJECTS) - $(CC) -shared $(OBJECTS) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -Wl,--image-base,0x64000000 -o $(TARGET).dll + $(CC) -shared $(OBJECTS) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -Wl,--image-base,0x74000000 -o $(TARGET).dll ## ## CLEAN RULES
--- a/libpurple/protocols/silc10/Makefile.mingw Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/silc10/Makefile.mingw Mon Nov 09 19:27:45 2009 +0000 @@ -7,6 +7,8 @@ PIDGIN_TREE_TOP := ../../.. include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak +DEFINES := $(subst -DWIN32_LEAN_AND_MEAN,,$(DEFINES)) + TARGET = libsilc NEEDED_DLLS = $(SILC_TOOLKIT)/lib/silc.dll \ $(SILC_TOOLKIT)/lib/silcclient.dll
--- a/libpurple/protocols/yahoo/libyahoo.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/yahoo/libyahoo.c Mon Nov 09 19:27:45 2009 +0000 @@ -249,7 +249,7 @@ yahoo_roomlist_get_list, yahoo_roomlist_cancel, yahoo_roomlist_expand_category, - NULL, /* can_receive_file */ + yahoo_can_receive_file, /* can_receive_file */ yahoo_send_file, yahoo_new_xfer, yahoo_offline_message, /* offline_message */
--- a/libpurple/protocols/yahoo/libymsg.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/yahoo/libymsg.c Mon Nov 09 19:27:45 2009 +0000 @@ -153,7 +153,8 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ } g_free(norm_bud); - - protocol = 0; + norm_bud=NULL; + fed = YAHOO_FEDERATION_NONE; stealth = 0; g_free(temp); temp = NULL; @@ -573,8 +594,8 @@ 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 @@ 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 @@ 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 @@ 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); + 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; } - else { - if (*stat == '1') - serv_got_typing(gc, from, 0, PURPLE_TYPING); - else - serv_got_typing_stopped(gc, from); - } + + 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ _("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 @@ 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 @@ } } + g_free(im->fed_from); g_free(im); - g_free(msn_from); } + g_slist_free(list); } @@ -1145,7 +1179,7 @@ PurpleConnection *gc; char *id; char *who; - int protocol; + YahooFederation fed; }; static void @@ -1156,16 +1190,24 @@ YahooData *yd = add_req->gc->proto_data; const char *who = add_req->who; - if (add_req->protocol == 2) + pkt = yahoo_packet_new(YAHOO_SERVICE_AUTH_REQ_15, YAHOO_STATUS_AVAILABLE, yd->session_id); + if (add_req->fed) { 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); + 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ } 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ } - 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 @@ 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 @@ 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 @@ } } - 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 @@ } 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); + 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; } - 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); - } + 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 @@ * 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 @@ /* 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 @@ { 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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_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 @@ 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_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; - - 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, "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 @@ 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 @@ if (!f) return; - if(f->protocol == 2) { - msn = TRUE; + if(f->fed) { temp = who+4; } else temp = who; @@ -4888,9 +4998,9 @@ } 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),
--- a/libpurple/protocols/yahoo/libymsg.h Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/yahoo/libymsg.h Mon Nov 09 19:27:45 2009 +0000 @@ -128,6 +128,20 @@ 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_convert_to_numeric(const char *str); +YahooFederation yahoo_get_federation_from_name(const char *who); /* yahoo_profile.c */ void yahoo_get_info(PurpleConnection *gc, const char *name);
--- a/libpurple/protocols/yahoo/util.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/yahoo/util.c Mon Nov 09 19:27:45 2009 +0000 @@ -881,6 +881,9 @@ } 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 @@ return g_string_free(dest, FALSE); } + +YahooFederation yahoo_get_federation_from_name(const char *who) +{ + YahooFederation fed = YAHOO_FEDERATION_NONE; + if (who[3] == '/') { + if (!g_ascii_strncasecmp(who, "msn", 3)) + fed = YAHOO_FEDERATION_MSN; + else if (!g_ascii_strncasecmp(who, "ocs", 3)) + fed = YAHOO_FEDERATION_OCS; + else if (!g_ascii_strncasecmp(who, "ibm", 3)) + fed = YAHOO_FEDERATION_IBM; + } + return fed; +} +
--- a/libpurple/protocols/yahoo/yahoo_filexfer.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/yahoo/yahoo_filexfer.c Mon Nov 09 19:27:45 2009 +0000 @@ -1070,6 +1070,13 @@ yahoo_packet_send_and_free(pkt, yd); } +gboolean yahoo_can_receive_file(PurpleConnection *gc, const char *who) +{ + if (!who || yahoo_get_federation_from_name(who) != YAHOO_FEDERATION_NONE) + return FALSE; + return TRUE; +} + void yahoo_send_file(PurpleConnection *gc, const char *who, const char *file) { struct yahoo_xfer_data *xfer_data;
--- a/libpurple/protocols/yahoo/yahoo_filexfer.h Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/yahoo/yahoo_filexfer.h Mon Nov 09 19:27:45 2009 +0000 @@ -43,6 +43,18 @@ PurpleXfer *yahoo_new_xfer(PurpleConnection *gc, const char *who); /** + * Returns TRUE if the buddy can receive file, FALSE otherwise. + * Federated users cannot receive files. So this will return FALSE only + * for them. + * + * @param gc The connection + * @param who The name of the remote user + * + * @return TRUE or FALSE + */ +gboolean yahoo_can_receive_file(PurpleConnection *gc, const char *who); + +/** * Send a file. * * @param gc The PurpleConnection handle.
--- a/libpurple/protocols/yahoo/yahoo_friend.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/yahoo/yahoo_friend.c Mon Nov 09 19:27:45 2009 +0000 @@ -151,9 +151,8 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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",
--- a/libpurple/protocols/yahoo/yahoo_friend.h Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/protocols/yahoo/yahoo_friend.h Mon Nov 09 19:27:45 2009 +0000 @@ -41,6 +41,7 @@ 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 @@ 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;
--- a/libpurple/server.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/server.c Mon Nov 09 19:27:45 2009 +0000 @@ -786,14 +786,14 @@ struct chat_invite_data *cid; int plugin_return; + g_return_if_fail(name != NULL); + g_return_if_fail(who != NULL); + account = purple_connection_get_account(gc); - if (PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc))->set_permit_deny == NULL) { - /* protocol does not support privacy, handle it ourselves */ - if (!purple_privacy_check(account, who)) { - purple_signal_emit(purple_conversations_get_handle(), "chat-invite-blocked", - account, who, name, message, data); - return; - } + if (!purple_privacy_check(account, who)) { + purple_signal_emit(purple_conversations_get_handle(), "chat-invite-blocked", + account, who, name, message, data); + return; } cid = g_new0(struct chat_invite_data, 1);
--- a/libpurple/tests/test_jabber_jutil.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/tests/test_jabber_jutil.c Mon Nov 09 19:27:45 2009 +0000 @@ -134,6 +134,14 @@ assert_invalid_jid("paul@2[::1]124/as"); assert_invalid_jid("paul@まつ.おおかみ/\x01"); + /* + * RFC 3454 Section 6 reads, in part, + * "If a string contains any RandALCat character, the + * string MUST NOT contain any LCat character." + * The character is U+066D (ARABIC FIVE POINTED STAR). + */ + assert_invalid_jid("foo@example.com/٭simplexe٭"); + /* Ensure that jabber_id_new is properly lowercasing node and domains */ assert_jid_parts("paul", "darkrain42.org", "PaUL@darkrain42.org"); assert_jid_parts("paul", "darkrain42.org", "paul@DaRkRaIn42.org");
--- a/libpurple/tests/test_yahoo_util.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/tests/test_yahoo_util.c Mon Nov 09 19:27:45 2009 +0000 @@ -180,6 +180,12 @@ 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
--- a/libpurple/theme-loader.c Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/theme-loader.c Mon Nov 09 19:27:45 2009 +0000 @@ -100,6 +100,7 @@ PurpleThemeLoaderPrivate *priv = PURPLE_THEME_LOADER_GET_PRIVATE(loader); g_free(priv->type); + g_free(priv); parent_class->finalize(obj); }
--- a/libpurple/win32/global.mak Mon Nov 09 19:27:38 2009 +0000 +++ b/libpurple/win32/global.mak Mon Nov 09 19:27:45 2009 +0000 @@ -85,7 +85,7 @@ DEFINES += -DHAVE_CYRUS_SASL endif -DEFINES += -DHAVE_CONFIG_H +DEFINES += -DHAVE_CONFIG_H -DWIN32_LEAN_AND_MEAN # Use -g flag when building debug version of Pidgin (including plugins). # Use -fnative-struct instead of -mms-bitfields when using mingw 1.1
--- a/pidgin.desktop.in Mon Nov 09 19:27:38 2009 +0000 +++ b/pidgin.desktop.in Mon Nov 09 19:27:45 2009 +0000 @@ -1,7 +1,7 @@ [Desktop Entry] _Name=Pidgin Internet Messenger _GenericName=Internet Messenger -_Comment=Send instant messages over multiple protocols +_Comment=Chat over IM. Supports AIM, Google Talk, Jabber/XMPP, MSN, Yahoo and more Exec=pidgin Icon=pidgin StartupNotify=true
--- a/pidgin/Makefile.mingw Mon Nov 09 19:27:38 2009 +0000 +++ b/pidgin/Makefile.mingw Mon Nov 09 19:27:45 2009 +0000 @@ -7,6 +7,8 @@ PIDGIN_TREE_TOP := .. include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak +DEFINES := $(subst -DWIN32_LEAN_AND_MEAN,,$(DEFINES)) + NEEDED_DLLS = $(GTKSPELL_TOP)/gtkspell/libgtkspell.dll ## @@ -55,12 +57,12 @@ ## PIDGIN_C_SRC = \ gtkaccount.c \ - gtkblist.c \ + gtkblist-theme-loader.c \ gtkblist-theme.c \ - gtkblist-theme-loader.c \ - gtkcertmgr.c \ + gtkblist.c \ gtkcellrendererexpander.c \ gtkcellrendererprogress.c \ + gtkcertmgr.c \ gtkconn.c \ gtkconv.c \ gtkdebug.c \ @@ -70,8 +72,8 @@ gtkeventloop.c \ gtkexpander.c \ gtkft.c \ + gtkicon-theme-loader.c \ gtkicon-theme.c \ - gtkicon-theme-loader.c \ gtkidle.c \ gtkimhtml.c \ gtkimhtmltoolbar.c \
--- a/pidgin/gtkblist.c Mon Nov 09 19:27:38 2009 +0000 +++ b/pidgin/gtkblist.c Mon Nov 09 19:27:45 2009 +0000 @@ -4184,6 +4184,12 @@ } } + if (hidden_conv) { + char *tmp = nametext; + nametext = g_strdup_printf("<b>%s</b>", tmp); + g_free(tmp); + } + /* Put it all together */ if ((!aliased || biglist) && (statustext || idletime)) { /* using <span size='smaller'> breaks the status, so it must be seperated into <small><span>*/
--- a/pidgin/gtkconn.c Mon Nov 09 19:27:38 2009 +0000 +++ b/pidgin/gtkconn.c Mon Nov 09 19:27:45 2009 +0000 @@ -142,7 +142,6 @@ { PurpleAccount *account = NULL; PidginAutoRecon *info; - GList *list; account = purple_connection_get_account(gc); info = g_hash_table_lookup(auto_reconns, account); @@ -164,17 +163,6 @@ purple_account_set_enabled(account, PIDGIN_UI, FALSE); } - - /* If we have any open chats, we probably want to rejoin when we get back online. */ - list = purple_get_chats(); - while (list) { - PurpleConversation *conv = list->data; - list = list->next; - if (conv->account != account || - purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv))) - continue; - purple_conversation_set_data(conv, "want-to-rejoin", GINT_TO_POINTER(TRUE)); - } } static void pidgin_connection_network_connected (void)
--- a/pidgin/gtkconv.c Mon Nov 09 19:27:38 2009 +0000 +++ b/pidgin/gtkconv.c Mon Nov 09 19:27:45 2009 +0000 @@ -4277,7 +4277,7 @@ /* Users */ for (; l != NULL; l = l->next) { tab_complete_process_item(&most_matched, entered, entered_bytes, &partial, nick_partial, - &matches, TRUE, ((PurpleConvChatBuddy *)l->data)->name); + &matches, FALSE, ((PurpleConvChatBuddy *)l->data)->name); } @@ -4812,6 +4812,9 @@ 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 @@ } } +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 @@ 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);
--- a/pidgin/gtkdialogs.c Mon Nov 09 19:27:38 2009 +0000 +++ b/pidgin/gtkdialogs.c Mon Nov 09 19:27:45 2009 +0000 @@ -73,69 +73,70 @@ /* 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 @@ 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
--- a/pidgin/gtkimhtml.c Mon Nov 09 19:27:38 2009 +0000 +++ b/pidgin/gtkimhtml.c Mon Nov 09 19:27:45 2009 +0000 @@ -5053,7 +5053,7 @@ It will be destroyed when 'anchor' is destroyed. */ anchor = gtk_text_buffer_create_child_anchor(imhtml->text_buffer, iter); g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_plaintext", text, g_free); - g_object_set_data(G_OBJECT(anchor), "gtkimhtml_tiptext", text); + g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_tiptext", g_strdup(text), g_free); g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_htmltext", g_strdup(smiley), g_free); /* This catches the expose events generated by animated @@ -5075,7 +5075,8 @@ gtk_container_add(GTK_CONTAINER(ebox), img); gtk_widget_show(img); g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_plaintext", text, g_free); - g_object_set_data(G_OBJECT(anchor), "gtkimhtml_tiptext", text); + g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_tiptext", + g_strdup(text), g_free); g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_htmltext", g_strdup(smiley), g_free); gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(imhtml), ebox, anchor); }
--- a/pidgin/gtkmain.c Mon Nov 09 19:27:38 2009 +0000 +++ b/pidgin/gtkmain.c Mon Nov 09 19:27:45 2009 +0000 @@ -143,6 +143,10 @@ } #ifdef HAVE_SIGNAL_H +static char *segfault_message; + +static int signal_sockets[2]; + static void sighandler(int sig); /* @@ -168,31 +172,60 @@ } } -char *segfault_message; +static void sighandler(int sig) +{ + ssize_t written; + + /* + * We won't do any of the heavy lifting for the signal handling here + * because we have no idea what was interrupted. Previously this signal + * handler could result in some calls to malloc/free, which can cause + * deadlock in libc when the signal handler was interrupting a previous + * malloc or free. So instead we'll do an ugly hack where we write the + * signal number to one end of a socket pair. The other half of the + * socket pair is watched by our main loop. When the main loop sees new + * data on the socket it reads in the signal and performs the appropriate + * action without fear of interrupting stuff. + */ + if (sig == SIGSEGV) { + fprintf(stderr, "%s", segfault_message); + abort(); + return; + } -/* - * This signal handler shouldn't be touching this much stuff. - * It should just set a flag and return, and something else in - * Pidgin should monitor the flag to see if something needs to - * be done. Because the signal handler interrupts the program, - * it could be called in the middle of adding a new connection - * to the list of connections, and then if we try to disconnect - * all connections it could lead to a crash because the linked - * list of connections could be in a weird state. But, well, - * this signal handler probably isn't called very often, so it's - * not a big deal. - */ -static void -sighandler(int sig) + written = write(signal_sockets[0], &sig, sizeof(int)); + if (written < 0 || written != sizeof(int)) { + /* This should never happen */ + purple_debug_error("sighandler", "Received signal %d but only " + "wrote %" G_GSSIZE_FORMAT " bytes out of %" + G_GSIZE_FORMAT ": %s\n", + sig, written, sizeof(int), g_strerror(errno)); + exit(1); + } +} + +static gboolean +mainloop_sighandler(GIOChannel *source, GIOCondition cond, gpointer data) { + GIOStatus stat; + int sig; + gsize bytes_read; + GError *error = NULL; + + /* read the signal number off of the io channel */ + stat = g_io_channel_read_chars(source, (gchar *)&sig, sizeof(int), + &bytes_read, &error); + if (stat != G_IO_STATUS_NORMAL) { + purple_debug_error("sighandler", "Signal callback failed to read " + "from signal socket: %s", error->message); + purple_core_quit(); + return FALSE; + } + switch (sig) { case SIGHUP: purple_debug_warning("sighandler", "Caught signal %d\n", sig); break; - case SIGSEGV: - fprintf(stderr, "%s", segfault_message); - abort(); - break; #if defined(USE_GSTREAMER) && !defined(GST_CAN_DISABLE_FORKING) /* By default, gstreamer forks when you initialize it, and waitpids for the * child. But if libpurple reaps the child rather than leaving it to @@ -219,6 +252,8 @@ purple_debug_warning("sighandler", "Caught signal %d\n", sig); purple_core_quit(); } + + return TRUE; } #endif @@ -502,11 +537,13 @@ sigset_t sigset; RETSIGTYPE (*prev_sig_disp)(int); char errmsg[BUFSIZ]; + GIOChannel *signal_channel; + GIOStatus signal_status; #ifndef DEBUG char *segfault_message_tmp; +#endif GError *error = NULL; #endif -#endif int opt; gboolean gui_check; gboolean debug_enabled; @@ -592,6 +629,29 @@ ); #endif + /* + * Create a socket pair for receiving unix signals from a signal + * handler. + */ + if (socketpair(AF_UNIX, SOCK_STREAM, 0, signal_sockets) < 0) { + perror("Failed to create sockets for GLib signal handling"); + exit(1); + } + signal_channel = g_io_channel_unix_new(signal_sockets[1]); + + /* + * Set the channel encoding to raw binary instead of the default of + * UTF-8, because we'll be sending integers across instead of strings. + */ + error = NULL; + signal_status = g_io_channel_set_encoding(signal_channel, NULL, &error); + if (signal_status != G_IO_STATUS_NORMAL) { + fprintf(stderr, "Failed to set the signal channel to raw " + "binary: %s", error->message); + exit(1); + } + g_io_add_watch(signal_channel, G_IO_IN, mainloop_sighandler, NULL); + /* Let's not violate any PLA's!!!! */ /* jseymour: whatever the fsck that means */ /* Robot101: for some reason things like gdm like to block * @@ -744,7 +804,7 @@ } #if GLIB_CHECK_VERSION(2,2,0) - g_set_application_name(_("Pidgin")); + g_set_application_name(PIDGIN_NAME); #endif /* glib-2.0 >= 2.2.0 */ #ifdef _WIN32
--- a/pidgin/gtkmedia.c Mon Nov 09 19:27:38 2009 +0000 +++ b/pidgin/gtkmedia.c Mon Nov 09 19:27:45 2009 +0000 @@ -89,6 +89,7 @@ GtkWidget *menubar; GtkWidget *statusbar; + GtkWidget *hold; GtkWidget *mute; GtkWidget *pause; @@ -187,6 +188,15 @@ } static void +pidgin_media_hold_toggled(GtkToggleButton *toggle, PidginMedia *media) +{ + purple_media_stream_info(media->priv->media, + gtk_toggle_button_get_active(toggle) ? + PURPLE_MEDIA_INFO_HOLD : PURPLE_MEDIA_INFO_UNHOLD, + NULL, NULL, TRUE); +} + +static void pidgin_media_mute_toggled(GtkToggleButton *toggle, PidginMedia *media) { purple_media_stream_info(media->priv->media, @@ -633,6 +643,16 @@ FALSE, FALSE, 0); gtk_widget_show(GTK_WIDGET(button_widget)); gtk_widget_show(send_widget); + + /* Hold button */ + gtkmedia->priv->hold = + gtk_toggle_button_new_with_mnemonic("_Hold"); + g_signal_connect(gtkmedia->priv->hold, "toggled", + G_CALLBACK(pidgin_media_hold_toggled), + gtkmedia); + gtk_box_pack_end(GTK_BOX(button_widget), gtkmedia->priv->hold, + FALSE, FALSE, 0); + gtk_widget_show(gtkmedia->priv->hold); } else { send_widget = gtkmedia->priv->send_widget; button_widget = gtkmedia->priv->button_widget;
--- a/pidgin/gtkpounce.c Mon Nov 09 19:27:38 2009 +0000 +++ b/pidgin/gtkpounce.c Mon Nov 09 19:27:45 2009 +0000 @@ -1455,7 +1455,7 @@ * Here we place the protocol name in the pounce dialog to lessen * confusion about what protocol a pounce is for. */ - tmp = g_strdup_printf( + tmp = g_strdup( (events & PURPLE_POUNCE_TYPING) ? _("Started typing") : (events & PURPLE_POUNCE_TYPED) ?
--- a/pidgin/gtkprefs.c Mon Nov 09 19:27:38 2009 +0000 +++ b/pidgin/gtkprefs.c Mon Nov 09 19:27:45 2009 +0000 @@ -77,6 +77,7 @@ static GtkListStore *smiley_theme_store = NULL; static GtkTreeSelection *smiley_theme_sel = NULL; static GtkWidget *prefs_proxy_frame = NULL; +static GtkWidget *prefs_proxy_subframe = NULL; static GtkWidget *prefs = NULL; static GtkWidget *debugbutton = NULL; @@ -624,7 +625,8 @@ _("The default Pidgin status icon theme")); gtk_list_store_set(prefs_status_icon_themes, &iter, 0, pixbuf, 1, tmp, 2, "", -1); g_free(tmp); - g_object_unref(G_OBJECT(pixbuf)); + if (pixbuf) + g_object_unref(G_OBJECT(pixbuf)); purple_theme_manager_for_each_theme(prefs_themes_sort); pref_sound_generate_markup(); @@ -1132,7 +1134,7 @@ gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT); - gtk_box_pack_start(GTK_BOX(ret), label, FALSE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(ret), label, TRUE, TRUE, 0); gtk_widget_show(label); sw = gtk_scrolled_window_new(NULL,NULL); @@ -1841,7 +1843,7 @@ 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 @@ pidgin_prefs_checkbox(_("_Enable automatic router port forwarding"), "/purple/network/map_ports", vbox); - ports_checkbox = pidgin_prefs_checkbox(_("_Manually specify range of ports to listen on"), - "/purple/network/ports_range_use", vbox); - - spin_button = pidgin_prefs_labeled_spin_button(vbox, _("_Start port:"), + hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); + + ports_checkbox = pidgin_prefs_checkbox(_("_Manually specify range of ports to listen on:"), + "/purple/network/ports_range_use", hbox); + + spin_button = pidgin_prefs_labeled_spin_button(hbox, _("_Start:"), "/purple/network/ports_range_start", 0, 65535, sg); if (!purple_prefs_get_bool("/purple/network/ports_range_use")) gtk_widget_set_sensitive(GTK_WIDGET(spin_button), FALSE); g_signal_connect(G_OBJECT(ports_checkbox), "clicked", G_CALLBACK(pidgin_toggle_sensitive), spin_button); - spin_button = pidgin_prefs_labeled_spin_button(vbox, _("_End port:"), + spin_button = pidgin_prefs_labeled_spin_button(hbox, _("_End:"), "/purple/network/ports_range_end", 0, 65535, sg); if (!purple_prefs_get_bool("/purple/network/ports_range_use")) gtk_widget_set_sensitive(GTK_WIDGET(spin_button), FALSE); g_signal_connect(G_OBJECT(ports_checkbox), "clicked", G_CALLBACK(pidgin_toggle_sensitive), spin_button); + pidgin_add_widget_to_vbox(GTK_BOX(vbox), NULL, NULL, hbox, TRUE, NULL); + g_object_unref(sg); /* TURN server */ @@ -1921,9 +1927,9 @@ pidgin_prefs_labeled_spin_button(hbox, _("_Port:"), "/purple/network/turn_port", 0, 65535, NULL); - hbox = pidgin_prefs_labeled_entry(vbox, _("_Username:"), + hbox = pidgin_prefs_labeled_entry(vbox, _("Use_rname:"), "/purple/network/turn_username", sg); - pidgin_prefs_labeled_password(hbox, _("_Password:"), + pidgin_prefs_labeled_password(hbox, _("Pass_word:"), "/purple/network/turn_password", NULL); if (purple_running_gnome()) { @@ -1967,9 +1973,15 @@ gtk_widget_show(browser_button); } else { vbox = pidgin_make_frame(ret, _("Proxy Server")); - prefs_proxy_frame = gtk_vbox_new(FALSE, 0); - - pidgin_prefs_dropdown(vbox, _("Proxy _type:"), PURPLE_PREF_STRING, + prefs_proxy_frame = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); + prefs_proxy_subframe = gtk_vbox_new(FALSE, 0); + + /* This is a global option that affects SOCKS4 usage even with account-specific proxy settings */ + pidgin_prefs_checkbox(_("Use remote _DNS with SOCKS4 proxies"), + "/purple/proxy/socks4_remotedns", prefs_proxy_frame); + gtk_box_pack_start(GTK_BOX(vbox), prefs_proxy_frame, 0, 0, 0); + + pidgin_prefs_dropdown(prefs_proxy_frame, _("Proxy t_ype:"), PURPLE_PREF_STRING, "/purple/proxy/type", _("No proxy"), "none", "SOCKS 4", "socks4", @@ -1977,21 +1989,17 @@ "HTTP", "http", _("Use Environmental Settings"), "envvar", NULL); - gtk_box_pack_start(GTK_BOX(vbox), prefs_proxy_frame, 0, 0, 0); + gtk_box_pack_start(GTK_BOX(prefs_proxy_frame), prefs_proxy_subframe, 0, 0, 0); proxy_info = purple_global_proxy_get_info(); purple_prefs_connect_callback(prefs, "/purple/proxy/type", - proxy_changed_cb, prefs_proxy_frame); - - /* This is a global option that affects SOCKS4 usage even with account-specific proxy settings */ - pidgin_prefs_checkbox(_("Use remote DNS with SOCKS4 proxies"), - "/purple/proxy/socks4_remotedns", prefs_proxy_frame); + proxy_changed_cb, prefs_proxy_subframe); table = gtk_table_new(4, 2, FALSE); gtk_container_set_border_width(GTK_CONTAINER(table), 0); gtk_table_set_col_spacings(GTK_TABLE(table), 5); gtk_table_set_row_spacings(GTK_TABLE(table), 10); - gtk_container_add(GTK_CONTAINER(prefs_proxy_frame), table); + gtk_container_add(GTK_CONTAINER(prefs_proxy_subframe), table); label = gtk_label_new_with_mnemonic(_("_Host:")); @@ -2012,11 +2020,11 @@ gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); pidgin_set_accessible_label (entry, label); - label = gtk_label_new_with_mnemonic(_("_Port:")); + label = gtk_label_new_with_mnemonic(_("P_ort:")); gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5); gtk_table_attach(GTK_TABLE(table), label, 2, 3, 0, 1, GTK_FILL, 0, 0, 0); - entry = gtk_entry_new(); + entry = gtk_spin_button_new_with_range(0, 65535, 1); gtk_label_set_mnemonic_widget(GTK_LABEL(label), entry); gtk_table_attach(GTK_TABLE(table), entry, 3, 4, 0, 1, GTK_FILL, 0, 0, 0); g_signal_connect(G_OBJECT(entry), "changed", @@ -2031,7 +2039,7 @@ } pidgin_set_accessible_label (entry, label); - label = gtk_label_new_with_mnemonic(_("_User:")); + label = gtk_label_new_with_mnemonic(_("User_name:")); gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5); gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2, GTK_FILL, 0, 0, 0);
--- a/pidgin/gtkrequest.c Mon Nov 09 19:27:38 2009 +0000 +++ b/pidgin/gtkrequest.c Mon Nov 09 19:27:45 2009 +0000 @@ -81,6 +81,33 @@ } PidginRequestData; static void +pidgin_widget_decorate_account(GtkWidget *cont, PurpleAccount *account) +{ + GtkWidget *image; + GdkPixbuf *pixbuf; + GtkTooltips *tips; + + if (!account) + return; + + pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL); + image = gtk_image_new_from_pixbuf(pixbuf); + g_object_unref(G_OBJECT(pixbuf)); + + tips = gtk_tooltips_new(); + gtk_tooltips_set_tip(tips, image, purple_account_get_username(account), NULL); + + if (GTK_IS_DIALOG(cont)) { + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(cont)->action_area), image, FALSE, TRUE, 0); + gtk_box_reorder_child(GTK_BOX(GTK_DIALOG(cont)->action_area), image, 0); + } else if (GTK_IS_HBOX(cont)) { + gtk_misc_set_alignment(GTK_MISC(image), 0, 0); + gtk_box_pack_end(GTK_BOX(cont), image, FALSE, TRUE, 0); + } + gtk_widget_show(image); +} + +static void generic_response_start(PidginRequestData *data) { g_return_if_fail(data != NULL); @@ -347,6 +374,8 @@ gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0); + pidgin_widget_decorate_account(hbox, account); + /* Descriptive label */ primary_esc = (primary != NULL) ? g_markup_escape_text(primary, -1) : NULL; secondary_esc = (secondary != NULL) ? g_markup_escape_text(secondary, -1) : NULL; @@ -515,6 +544,8 @@ gtk_misc_set_alignment(GTK_MISC(img), 0, 0); gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0); + pidgin_widget_decorate_account(hbox, account); + /* Vertical box */ vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0); @@ -639,6 +670,8 @@ vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0); + pidgin_widget_decorate_account(hbox, account); + /* Descriptive label */ primary_esc = (primary != NULL) ? g_markup_escape_text(primary, -1) : NULL; secondary_esc = (secondary != NULL) ? g_markup_escape_text(secondary, -1) : NULL; @@ -1144,6 +1177,8 @@ GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT); gtk_window_set_default(GTK_WINDOW(win), button); + pidgin_widget_decorate_account(hbox, account); + /* Setup the vbox */ vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
--- a/pidgin/gtkroomlist.c Mon Nov 09 19:27:38 2009 +0000 +++ b/pidgin/gtkroomlist.c Mon Nov 09 19:27:45 2009 +0000 @@ -111,7 +111,18 @@ static void dialog_select_account_cb(GObject *w, PurpleAccount *account, PidginRoomlistDialog *dialog) { + gboolean change = (account != dialog->account); dialog->account = account; + + if (change && dialog->roomlist) { + PidginRoomlist *rl = dialog->roomlist->ui_data; + if (rl->tree) { + gtk_widget_destroy(rl->tree); + rl->tree = NULL; + } + purple_roomlist_unref(dialog->roomlist); + dialog->roomlist = NULL; + } } static void list_button_cb(GtkButton *button, PidginRoomlistDialog *dialog)
--- a/pidgin/gtkstatusbox.c Mon Nov 09 19:27:38 2009 +0000 +++ b/pidgin/gtkstatusbox.c Mon Nov 09 19:27:45 2009 +0000 @@ -79,8 +79,8 @@ static void pidgin_status_box_pulse_typing(PidginStatusBox *status_box); static void pidgin_status_box_refresh(PidginStatusBox *status_box); -static void status_menu_refresh_iter(PidginStatusBox *status_box); -static void pidgin_status_box_regenerate(PidginStatusBox *status_box); +static void status_menu_refresh_iter(PidginStatusBox *status_box, gboolean status_changed); +static void pidgin_status_box_regenerate(PidginStatusBox *status_box, gboolean status_changed); static void pidgin_status_box_changed(PidginStatusBox *box); static void pidgin_status_box_size_request (GtkWidget *widget, GtkRequisition *requisition); static void pidgin_status_box_size_allocate (GtkWidget *widget, GtkAllocation *allocation); @@ -304,7 +304,7 @@ if (status_box->account == account) update_to_reflect_account_status(status_box, account, newstatus); else if (status_box->token_status_account == account) - status_menu_refresh_iter(status_box); + status_menu_refresh_iter(status_box, TRUE); } static gboolean @@ -312,6 +312,7 @@ { if (event->button == 3) { GtkWidget *menu_item; + const char *path; if (box->icon_box_menu) gtk_widget_destroy(box->icon_box_menu); @@ -325,7 +326,8 @@ menu_item = pidgin_new_item_from_stock(box->icon_box_menu, _("Remove"), GTK_STOCK_REMOVE, G_CALLBACK(remove_buddy_icon_cb), box, 0, 0, NULL); - if (purple_prefs_get_path(PIDGIN_PREFS_ROOT "/accounts/buddyicon") == NULL) + if (!(path = purple_prefs_get_path(PIDGIN_PREFS_ROOT "/accounts/buddyicon")) + || !*path) gtk_widget_set_sensitive(menu_item, FALSE); gtk_menu_popup(GTK_MENU(box->icon_box_menu), NULL, NULL, NULL, NULL, @@ -559,7 +561,7 @@ else statusbox->token_status_account = check_active_accounts_for_identical_statuses(); - pidgin_status_box_regenerate(statusbox); + pidgin_status_box_regenerate(statusbox, TRUE); break; default: @@ -821,7 +823,7 @@ * keyboard signals instead of the changed signal? */ static void -status_menu_refresh_iter(PidginStatusBox *status_box) +status_menu_refresh_iter(PidginStatusBox *status_box, gboolean status_changed) { PurpleSavedStatus *saved_status; PurpleStatusPrimitive primitive; @@ -912,18 +914,15 @@ } else status_box->active_row = NULL; - message = purple_savedstatus_get_message(saved_status); - if (!purple_savedstatus_is_transient(saved_status) || !message || !*message) - { - status_box->imhtml_visible = FALSE; - gtk_widget_hide_all(status_box->vbox); - } - else - { - status_box->imhtml_visible = TRUE; - gtk_widget_show_all(status_box->vbox); + if (status_changed) { + message = purple_savedstatus_get_message(saved_status); /* + * If we are going to hide the imhtml, don't retain the + * message because showing the old message later is + * confusing. If we are going to set the message to a pre-set, + * then we need to do this anyway + * * Suppress the "changed" signal because the status * was changed programmatically. */ @@ -931,12 +930,24 @@ gtk_imhtml_clear(GTK_IMHTML(status_box->imhtml)); gtk_imhtml_clear_formatting(GTK_IMHTML(status_box->imhtml)); - gtk_imhtml_append_text(GTK_IMHTML(status_box->imhtml), message, 0); + + if (!purple_savedstatus_is_transient(saved_status) || !message || !*message) + { + status_box->imhtml_visible = FALSE; + gtk_widget_hide_all(status_box->vbox); + } + else + { + status_box->imhtml_visible = TRUE; + gtk_widget_show_all(status_box->vbox); + + gtk_imhtml_append_text(GTK_IMHTML(status_box->imhtml), message, 0); + } + gtk_widget_set_sensitive(GTK_WIDGET(status_box->imhtml), TRUE); + update_size(status_box); } - update_size(status_box); - /* Stop suppressing the "changed" signal. */ gtk_widget_set_sensitive(GTK_WIDGET(status_box), TRUE); } @@ -996,50 +1007,50 @@ * statuses and a token account if they do */ static PurpleAccount* check_active_accounts_for_identical_statuses(void) { - PurpleAccount *acct = NULL, *acct2; - GList *tmp, *tmp2, *active_accts = purple_accounts_get_all_active(); - GList *s, *s1, *s2; - - for (tmp = active_accts; tmp; tmp = tmp->next) { - acct = tmp->data; - s = purple_account_get_status_types(acct); - for (tmp2 = tmp->next; tmp2; tmp2 = tmp2->next) { - acct2 = tmp2->data; - - /* Only actually look at the statuses if the accounts use the same prpl */ - if (strcmp(purple_account_get_protocol_id(acct), purple_account_get_protocol_id(acct2))) { - acct = NULL; - break; - } - - s2 = purple_account_get_status_types(acct2); - - s1 = s; - while (s1 && s2) { - PurpleStatusType *st1 = s1->data, *st2 = s2->data; - /* TODO: Are these enough to consider the statuses identical? */ - if (purple_status_type_get_primitive(st1) != purple_status_type_get_primitive(st2) - || strcmp(purple_status_type_get_id(st1), purple_status_type_get_id(st2)) - || strcmp(purple_status_type_get_name(st1), purple_status_type_get_name(st2))) { - acct = NULL; - break; - } - - s1 = s1->next; - s2 = s2->next; - } - - if (s1 != s2) {/* Will both be NULL if matched */ - acct = NULL; + GList *iter, *active_accts = purple_accounts_get_all_active(); + PurpleAccount *acct1 = NULL; + const char *prpl1 = NULL; + + if (active_accts) { + acct1 = active_accts->data; + prpl1 = purple_account_get_protocol_id(acct1); + } else { + /* there's no enabled account */ + return NULL; + } + + /* start at the second account */ + for (iter = active_accts->next; iter; iter = iter->next) { + PurpleAccount *acct2 = iter->data; + GList *s1, *s2; + + if (!g_str_equal(prpl1, purple_account_get_protocol_id(acct2))) { + acct1 = NULL; + break; + } + + for (s1 = purple_account_get_status_types(acct1), + s2 = purple_account_get_status_types(acct2); s1 && s2; + s1 = s1->next, s2 = s2->next) { + PurpleStatusType *st1 = s1->data, *st2 = s2->data; + /* TODO: Are these enough to consider the statuses identical? */ + if (purple_status_type_get_primitive(st1) != purple_status_type_get_primitive(st2) + || strcmp(purple_status_type_get_id(st1), purple_status_type_get_id(st2)) + || strcmp(purple_status_type_get_name(st1), purple_status_type_get_name(st2))) { + acct1 = NULL; break; } } - if (!acct) + + if (s1 != s2) {/* Will both be NULL if matched */ + acct1 = NULL; break; + } } + g_list_free(active_accts); - return acct; + return acct1; } static void @@ -1068,7 +1079,7 @@ } static void -pidgin_status_box_regenerate(PidginStatusBox *status_box) +pidgin_status_box_regenerate(PidginStatusBox *status_box, gboolean status_changed) { GtkIconSize icon_size; @@ -1104,7 +1115,7 @@ pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box), PIDGIN_STATUS_BOX_TYPE_CUSTOM, NULL, _("New status..."), NULL, NULL); pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box), PIDGIN_STATUS_BOX_TYPE_SAVED, NULL, _("Saved statuses..."), NULL, NULL); - status_menu_refresh_iter(status_box); + status_menu_refresh_iter(status_box, status_changed); pidgin_status_box_refresh(status_box); } else { @@ -1156,7 +1167,7 @@ update_to_reflect_account_status(status_box, status_box->account, purple_account_get_active_status(status_box->account)); else { - status_menu_refresh_iter(status_box); + status_menu_refresh_iter(status_box, TRUE); pidgin_status_box_refresh(status_box); } return TRUE; @@ -1229,7 +1240,7 @@ /* Regenerate the list if it has changed */ if (initial_token_acct != status_box->token_status_account) { - pidgin_status_box_regenerate(status_box); + pidgin_status_box_regenerate(status_box, TRUE); } } @@ -1238,13 +1249,14 @@ current_savedstatus_changed_cb(PurpleSavedStatus *now, PurpleSavedStatus *old, PidginStatusBox *status_box) { /* Make sure our current status is added to the list of popular statuses */ - pidgin_status_box_regenerate(status_box); + pidgin_status_box_regenerate(status_box, TRUE); } static void saved_status_updated_cb(PurpleSavedStatus *status, PidginStatusBox *status_box) { - pidgin_status_box_regenerate(status_box); + pidgin_status_box_regenerate(status_box, + purple_savedstatus_get_current() == status); } static void @@ -1919,7 +1931,7 @@ status_box->token_status_account = check_active_accounts_for_identical_statuses(); cache_pixbufs(status_box); - pidgin_status_box_regenerate(status_box); + pidgin_status_box_regenerate(status_box, TRUE); purple_signal_connect(purple_savedstatuses_get_handle(), "savedstatus-changed", status_box, @@ -2324,18 +2336,6 @@ pidgin_status_box_refresh(status_box); } -static gboolean -message_changed(const char *one, const char *two) -{ - if (one == NULL && two == NULL) - return FALSE; - - if (one == NULL || two == NULL) - return TRUE; - - return (g_utf8_collate(one, two) != 0); -} - static void activate_currently_selected_status(PidginStatusBox *status_box) { @@ -2386,6 +2386,7 @@ if (status_box->account == NULL) { PurpleStatusType *acct_status_type = NULL; + const char *id = NULL; /* id of acct_status_type */ PurpleStatusPrimitive primitive = GPOINTER_TO_INT(data); /* Global */ /* Save the newly selected status to prefs.xml and status.xml */ @@ -2394,7 +2395,6 @@ if (status_box->token_status_account) { gint active; PurpleStatus *status; - const char *id = NULL; GtkTreePath *path = gtk_tree_row_reference_get_path(status_box->active_row); active = gtk_tree_path_get_indices(path)[0]; @@ -2402,37 +2402,35 @@ status = purple_account_get_active_status(status_box->token_status_account); - acct_status_type = find_status_type_by_index(status_box->token_status_account, active); + acct_status_type = find_status_type_by_index(status_box->token_status_account, active); id = purple_status_type_get_id(acct_status_type); - if (strncmp(id, purple_status_get_id(status), strlen(id)) == 0) + if (g_str_equal(id, purple_status_get_id(status)) && + purple_strequal(message, purple_status_get_attr_string(status, "message"))) { /* Selected status and previous status is the same */ - if (!message_changed(message, purple_status_get_attr_string(status, "message"))) - { - PurpleSavedStatus *ss = purple_savedstatus_get_current(); - /* Make sure that statusbox displays the correct thing. - * It can get messed up if the previous selection was a - * saved status that wasn't supported by this account */ - if ((purple_savedstatus_get_type(ss) == primitive) - && purple_savedstatus_is_transient(ss) - && purple_savedstatus_has_substatuses(ss)) - changed = FALSE; - } + PurpleSavedStatus *ss = purple_savedstatus_get_current(); + /* Make sure that statusbox displays the correct thing. + * It can get messed up if the previous selection was a + * saved status that wasn't supported by this account */ + if ((purple_savedstatus_get_type(ss) == primitive) + && purple_savedstatus_is_transient(ss) + && purple_savedstatus_has_substatuses(ss)) + changed = FALSE; } } else { saved_status = purple_savedstatus_get_current(); if (purple_savedstatus_get_type(saved_status) == primitive && - !purple_savedstatus_has_substatuses(saved_status)) + !purple_savedstatus_has_substatuses(saved_status) && + purple_strequal(purple_savedstatus_get_message(saved_status), message)) { - if (!message_changed(purple_savedstatus_get_message(saved_status), message)) - changed = FALSE; + changed = FALSE; } } if (changed) { - /* Manually find the appropriate transient acct */ + /* Manually find the appropriate transient status */ if (status_box->token_status_account) { GList *iter = purple_savedstatuses_get_all(); GList *tmp, *active_accts = purple_accounts_get_all_active(); @@ -2440,27 +2438,31 @@ for (; iter != NULL; iter = iter->next) { PurpleSavedStatus *ss = iter->data; const char *ss_msg = purple_savedstatus_get_message(ss); + /* find a known transient status that is the same as the + * new selected one */ if ((purple_savedstatus_get_type(ss) == primitive) && purple_savedstatus_is_transient(ss) && purple_savedstatus_has_substatuses(ss) && /* Must have substatuses */ - !message_changed(ss_msg, message)) + purple_strequal(ss_msg, message)) { gboolean found = FALSE; - /* The currently enabled accounts must have substatuses for all the active accts */ + /* this status must have substatuses for all the active accts */ for(tmp = active_accts; tmp != NULL; tmp = tmp->next) { PurpleAccount *acct = tmp->data; PurpleSavedStatusSub *sub = purple_savedstatus_get_substatus(ss, acct); if (sub) { const PurpleStatusType *sub_type = purple_savedstatus_substatus_get_type(sub); const char *subtype_status_id = purple_status_type_get_id(sub_type); - if (subtype_status_id && !strcmp(subtype_status_id, - purple_status_type_get_id(acct_status_type))) + if (purple_strequal(subtype_status_id, id)) { found = TRUE; + break; + } } } - if (!found) - continue; - saved_status = ss; - break; + + if (found) { + saved_status = ss; + break; + } } } @@ -2503,11 +2505,11 @@ status_type = find_status_type_by_index(status_box->account, active); id = purple_status_type_get_id(status_type); - if (strncmp(id, purple_status_get_id(status), strlen(id)) == 0) + if (g_str_equal(id, purple_status_get_id(status)) && + purple_strequal(message, purple_status_get_attr_string(status, "message"))) { /* Selected status and previous status is the same */ - if (!message_changed(message, purple_status_get_attr_string(status, "message"))) - changed = FALSE; + changed = FALSE; } if (changed) @@ -2597,7 +2599,7 @@ if (status_box->typing == 0) { /* Nothing has changed, so we don't need to do anything */ - status_menu_refresh_iter(status_box); + status_menu_refresh_iter(status_box, FALSE); return; } @@ -2655,14 +2657,14 @@ pidgin_status_editor_show(FALSE, purple_savedstatus_is_transient(saved_status) ? saved_status : NULL); - status_menu_refresh_iter(status_box); + status_menu_refresh_iter(status_box, FALSE); return; } if (type == PIDGIN_STATUS_BOX_TYPE_SAVED) { pidgin_status_window_show(); - status_menu_refresh_iter(status_box); + status_menu_refresh_iter(status_box, FALSE); return; } }
--- a/pidgin/gtkutils.c Mon Nov 09 19:27:38 2009 +0000 +++ b/pidgin/gtkutils.c Mon Nov 09 19:27:45 2009 +0000 @@ -75,7 +75,7 @@ } AopMenu; static guint accels_save_timer = 0; -static GList *gnome_url_handlers = NULL; +static GSList *registered_url_handlers = NULL; static gboolean url_clicked_idle_cb(gpointer data) @@ -3890,7 +3890,7 @@ start += sizeof("/desktop/gnome/url-handlers/") - 1; protocol = g_strdup_printf("%s:", start); - gnome_url_handlers = g_list_prepend(gnome_url_handlers, protocol); + registered_url_handlers = g_slist_prepend(registered_url_handlers, protocol); gtk_imhtml_class_register_protocol(protocol, url_clicked_cb, link_context_menu); } start = c + 1; @@ -3898,9 +3898,45 @@ } g_free(tmp); - return (gnome_url_handlers != NULL); + return (registered_url_handlers != NULL); } +#ifdef _WIN32 +static void +winpidgin_register_win32_url_handlers(void) +{ + int idx = 0; + LONG ret = ERROR_SUCCESS; + + do { + DWORD nameSize = 256; + char start[256]; + /* I don't think we need to worry about non-ASCII protocol names */ + ret = RegEnumKeyExA(HKEY_CLASSES_ROOT, idx++, start, &nameSize, + NULL, NULL, NULL, NULL); + if (ret == ERROR_SUCCESS) { + HKEY reg_key = NULL; + ret = RegOpenKeyExA(HKEY_CLASSES_ROOT, start, 0, KEY_READ, ®_key); + if (ret == ERROR_SUCCESS) { + ret = RegQueryValueExA(reg_key, "URL Protocol", NULL, NULL, NULL, NULL); + if (ret == ERROR_SUCCESS) { + gchar *protocol = g_strdup_printf("%s:", start); + registered_url_handlers = g_slist_prepend(registered_url_handlers, protocol); + /* We still pass everything to the "http" "open" handler for security reasons */ + gtk_imhtml_class_register_protocol(protocol, url_clicked_cb, link_context_menu); + } + RegCloseKey(reg_key); + } + ret = ERROR_SUCCESS; + } + } while (ret == ERROR_SUCCESS); + + if (ret != ERROR_NO_MORE_ITEMS) + purple_debug_error("winpidgin", "Error iterating HKEY_CLASSES_ROOT subkeys: %ld\n", + ret); +} +#endif + void pidgin_utils_init(void) { gtk_imhtml_class_register_protocol("http://", url_clicked_cb, link_context_menu); @@ -3918,6 +3954,11 @@ /* If we're under GNOME, try registering the system URL handlers. */ if (purple_running_gnome()) register_gnome_url_handlers(); + +#ifdef _WIN32 + winpidgin_register_win32_url_handlers(); +#endif + } void pidgin_utils_uninit(void) @@ -3925,16 +3966,16 @@ gtk_imhtml_class_register_protocol("open://", NULL, NULL); /* If we have GNOME handlers registered, unregister them. */ - if (gnome_url_handlers) + if (registered_url_handlers) { - GList *l; - for (l = gnome_url_handlers ; l ; l = l->next) + GSList *l; + for (l = registered_url_handlers; l; l = l->next) { gtk_imhtml_class_register_protocol((char *)l->data, NULL, NULL); g_free(l->data); } - g_list_free(gnome_url_handlers); - gnome_url_handlers = NULL; + g_slist_free(registered_url_handlers); + registered_url_handlers = NULL; return; }
--- a/pidgin/pixmaps/Makefile.am Mon Nov 09 19:27:38 2009 +0000 +++ b/pidgin/pixmaps/Makefile.am Mon Nov 09 19:27:45 2009 +0000 @@ -236,6 +236,7 @@ protocols/16/jabber.png \ protocols/16/meanwhile.png \ protocols/16/msn.png \ + protocols/16/mxit.png \ protocols/16/myspace.png \ protocols/16/qq.png \ protocols/16/silc.png \ @@ -308,6 +309,7 @@ protocols/48/jabber.png \ protocols/48/meanwhile.png \ protocols/48/msn.png \ + protocols/48/mxit.png \ protocols/48/myspace.png \ protocols/48/qq.png \ protocols/48/silc.png \ @@ -326,6 +328,7 @@ protocols/scalable/jabber.svg \ protocols/scalable/meanwhile.svg \ protocols/scalable/msn.svg \ + protocols/scalable/mxit.svg \ protocols/scalable/qq.svg \ protocols/scalable/silc.svg \ protocols/scalable/simple.svg \
--- a/pidgin/pixmaps/emotes/default/24/default.theme.in Mon Nov 09 19:27:38 2009 +0000 +++ b/pidgin/pixmaps/emotes/default/24/default.theme.in Mon Nov 09 19:27:45 2009 +0000 @@ -491,7 +491,7 @@ 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 :( :-( =(
--- a/pidgin/pixmaps/emotes/small/16/small.theme.in Mon Nov 09 19:27:38 2009 +0000 +++ b/pidgin/pixmaps/emotes/small/16/small.theme.in Mon Nov 09 19:27:45 2009 +0000 @@ -188,7 +188,7 @@ 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 :( :-( =(
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/pixmaps/protocols/scalable/mxit.svg Mon Nov 09 19:27:45 2009 +0000 @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) --> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> +<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="48px" height="48px" viewBox="0 0 48 48" enable-background="new 0 0 48 48" xml:space="preserve"> +<image overflow="visible" width="34" height="39" xlink:href="data:image/jpeg;base64,/9j/4AAQSkZJRgABAgEASABIAAD/7AARRHVja3kAAQAEAAAAHgAA/+4AIUFkb2JlAGTAAAAAAQMA +EAMCAwYAAAH0AAACSgAAA6n/2wCEABALCwsMCxAMDBAXDw0PFxsUEBAUGx8XFxcXFx8eFxoaGhoX +Hh4jJSclIx4vLzMzLy9AQEBAQEBAQEBAQEBAQEABEQ8PERMRFRISFRQRFBEUGhQWFhQaJhoaHBoa +JjAjHh4eHiMwKy4nJycuKzU1MDA1NUBAP0BAQEBAQEBAQEBAQP/CABEIACgAIwMBIgACEQEDEQH/ +xACyAAEAAwEBAAAAAAAAAAAAAAAAAQMFBAIBAAMBAQAAAAAAAAAAAAAAAAABAgMEEAABBAECBgMB +AAAAAAAAAAABAAIDBAUREyESIhQVRRAjNQYRAAEDAgMDBwgLAAAAAAAAAAERAgMAEiETBDFRIkFh +cUIUhcWhMlKDs9M01GKSIzNzwyRkhJQVEgABAwAGCQUAAAAAAAAAAAAAARECIUFRYYESEDFxodEy +QoKicjNDNIT/2gAMAwEAAhEDEQAAANWbtDvwx51+eStam/NlFqUEyBo7eck8dAFoyr//2gAIAQIA +AQUAe+RpEry7QJ4POA4FDeR3V9q//9oACAEDAAEFAAAUWjRDTTh8dC6V0r//2gAIAQEAAQUAiihn +hNOsu1rBMr0HLks9hi7UdSE5ytyx5evM/J6dx6rCcInfp3ON2+CZfVY1z46RMm9IHyveZpW+q8Vc +rtkr51qbF/QEx0s04eCg2//aAAgBAgIGPwBoxWSbRIyisXvNSfZsJrbFYkFblRlP1FOTHK+J8fid +XuefE//aAAgBAwIGPwCmTDpJzsE9Tkrzs0VlXLuP/9oACAEBAQY/AIZ54GanU6mJk8sksTZnEyNa +9Be11rW3IAMK+ChH8SP3dY6KH+pH7uvhNORy2wxscOhzGtc01n9rmvzP85bup2zs2b+JZ1q0csgJ +adHC3hRVMcR5SN1XWSIqLw7fr0GNa8KQ242oC7Aea4moCAFc14cd4FqV3n4lWjP7KL2cNXfT/Kph +3ZPtDUL0Nrbmk7i61K7z8SrQyNAd+khaQSm2OPmO6s5G3XXWrgllm1KLyjCjbUN2LSXLsFBha0BW +kkOJ80g7Leau8vEqy9HPEdO0/ZRTxOe6NvoNeyWPhHIow31wt0rvVS/MVjHph6qX5iuKXTQr1mwv +Lhzi+dwXpFZWdNZk5SX9fMzs/Z95fivkr//Z" transform="matrix(0.9999 0 0 0.9999 7.0146 6.0142)"> +</image> +</svg>
--- a/pidgin/plugins/disco/gtkdisco.c Mon Nov 09 19:27:38 2009 +0000 +++ b/pidgin/plugins/disco/gtkdisco.c Mon Nov 09 19:27:45 2009 +0000 @@ -141,8 +141,18 @@ static void dialog_select_account_cb(GObject *w, PurpleAccount *account, PidginDiscoDialog *dialog) { + gboolean change = (account != dialog->account); dialog->account = account; gtk_widget_set_sensitive(dialog->browse_button, account != NULL); + + if (change && dialog->discolist) { + if (dialog->discolist->tree) { + gtk_widget_destroy(dialog->discolist->tree); + dialog->discolist->tree = NULL; + } + pidgin_disco_list_unref(dialog->discolist); + dialog->discolist = NULL; + } } static void register_button_cb(GtkWidget *unused, PidginDiscoDialog *dialog) @@ -152,12 +162,15 @@ static void discolist_cancel_cb(PidginDiscoList *pdl, const char *server) { + pdl->dialog->prompt_handle = NULL; + pidgin_disco_list_set_in_progress(pdl, FALSE); pidgin_disco_list_unref(pdl); } static void discolist_ok_cb(PidginDiscoList *pdl, const char *server) { + pdl->dialog->prompt_handle = NULL; gtk_widget_set_sensitive(pdl->dialog->browse_button, TRUE); if (!server || !*server) { @@ -226,7 +239,7 @@ /* Note to translators: The string "Enter an XMPP Server" is asking the user to type the name of an XMPP server which will then be queried */ - purple_request_input(my_plugin, _("Server name request"), _("Enter an XMPP Server"), + dialog->prompt_handle = purple_request_input(my_plugin, _("Server name request"), _("Enter an XMPP Server"), _("Select an XMPP server to query"), server, FALSE, FALSE, NULL, _("Find Services"), PURPLE_CALLBACK(discolist_ok_cb), @@ -380,6 +393,9 @@ PidginDiscoDialog *dialog = d; PidginDiscoList *list = dialog->discolist; + if (dialog->prompt_handle) + purple_request_close(PURPLE_REQUEST_INPUT, dialog->prompt_handle); + if (list) { list->dialog = NULL;
--- a/pidgin/plugins/disco/gtkdisco.h Mon Nov 09 19:27:38 2009 +0000 +++ b/pidgin/plugins/disco/gtkdisco.h Mon Nov 09 19:27:45 2009 +0000 @@ -43,6 +43,8 @@ PurpleAccount *account; PidginDiscoList *discolist; + + gpointer *prompt_handle; }; struct _PidginDiscoList {
--- a/pidgin/plugins/perl/common/Makefile.mingw Mon Nov 09 19:27:38 2009 +0000 +++ b/pidgin/plugins/perl/common/Makefile.mingw Mon Nov 09 19:27:45 2009 +0000 @@ -5,9 +5,12 @@ # PIDGIN_TREE_TOP := ../../../.. -GCCWARNINGS := -Wno-comment -Waggregate-return -Wcast-align -Wdeclaration-after-statement -Werror-implicit-function-declaration -Wextra -Wno-sign-compare -Wno-unused-parameter -Winit-self -Wmissing-declarations -Wmissing-prototypes -Wpointer-arith -Wundef -Wno-unused include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak +GCCWARNINGS += -Wno-comment -Wno-unused -Wno-nested-externs + +DEFINES := $(subst -DWIN32_LEAN_AND_MEAN,,$(DEFINES)) + TARGET = Pidgin EXTUTILS ?= C:/perl/lib/ExtUtils
--- a/pidgin/plugins/win32/winprefs/Makefile.mingw Mon Nov 09 19:27:38 2009 +0000 +++ b/pidgin/plugins/win32/winprefs/Makefile.mingw Mon Nov 09 19:27:45 2009 +0000 @@ -8,6 +8,7 @@ include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak TARGET = winprefs +DEFINES := $(subst -DWIN32_LEAN_AND_MEAN,,$(DEFINES)) DEFINES += -DWINVER=0x500 ##
--- a/pidgin/plugins/xmppconsole.c Mon Nov 09 19:27:38 2009 +0000 +++ b/pidgin/plugins/xmppconsole.c Mon Nov 09 19:27:45 2009 +0000 @@ -660,7 +660,6 @@ 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) {
--- a/pidgin/win32/nsis/pidgin-installer.nsi Mon Nov 09 19:27:38 2009 +0000 +++ b/pidgin/win32/nsis/pidgin-installer.nsi Mon Nov 09 19:27:45 2009 +0000 @@ -717,6 +717,7 @@ 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"
--- a/po/ChangeLog Mon Nov 09 19:27:38 2009 +0000 +++ b/po/ChangeLog Mon Nov 09 19:27:45 2009 +0000 @@ -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)
--- a/po/POTFILES.in Mon Nov 09 19:27:38 2009 +0000 +++ b/po/POTFILES.in Mon Nov 09 19:27:45 2009 +0000 @@ -126,6 +126,15 @@ libpurple/protocols/msnp9/state.c libpurple/protocols/msnp9/switchboard.c libpurple/protocols/msnp9/userlist.c +libpurple/protocols/mxit/actions.c +libpurple/protocols/mxit/filexfer.c +libpurple/protocols/mxit/http.c +libpurple/protocols/mxit/login.c +libpurple/protocols/mxit/mxit.c +libpurple/protocols/mxit/profile.c +libpurple/protocols/mxit/protocol.c +libpurple/protocols/mxit/roster.c +libpurple/protocols/mxit/splashscreen.c libpurple/protocols/myspace/myspace.c libpurple/protocols/myspace/user.c libpurple/protocols/myspace/zap.c
--- a/po/ca.po Mon Nov 09 19:27:38 2009 +0000 +++ b/po/ca.po Mon Nov 09 19:27:45 2009 +0000 @@ -33,8 +33,8 @@ msgstr "" "Project-Id-Version: Pidgin\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2009-09-07 18:26-0700\n" -"PO-Revision-Date: 2009-07-26 17:23+0200\n" +"POT-Creation-Date: 2009-10-25 17:57+0100\n" +"PO-Revision-Date: 2009-10-26 09:20+0100\n" "Last-Translator: Josep Puigdemont i Casamajó <josep.puigdemont@gmail.com>\n" "Language-Team: Catalan <tradgnome@softcatala.net>\n" "MIME-Version: 1.0\n" @@ -576,13 +576,6 @@ msgid "Re-enable Account" msgstr "Rehabilita el compte" -msgid "" -"The account has disconnected and you are no longer in this chat. You will be " -"automatically rejoined in the chat when the account reconnects." -msgstr "" -"El compte s'ha desconnectat i ja no sou al xat. Quan es torni a connectar el " -"compte entrareu de nou automàticament al xat." - msgid "No such command." msgstr "No existeix l'ordre." @@ -625,6 +618,13 @@ msgid "You have left this chat." msgstr "Heu sortit d'aquest xat" +msgid "" +"The account has disconnected and you are no longer in this chat. You will be " +"automatically rejoined in the chat when the account reconnects." +msgstr "" +"El compte s'ha desconnectat i ja no sou al xat. Quan es torni a connectar el " +"compte entrareu de nou automàticament al xat." + msgid "Logging started. Future messages in this conversation will be logged." msgstr "" "S'ha iniciat el registre. Es registraran els propers missatges d'aquesta " @@ -661,6 +661,9 @@ msgid "Enable Sounds" msgstr "Habilita els sons" +msgid "You are not connected." +msgstr "No esteu connectat." + msgid "<AUTO-REPLY> " msgstr "<RESPOSTA-AUTOMÀTICA> " @@ -670,8 +673,8 @@ msgstr[0] "Llista d'%d usuari:\n" msgstr[1] "Llista de %d usuaris:\n" -msgid "Supported debug options are: version" -msgstr "Les opcions de depuració disponibles són: version" +msgid "Supported debug options are: plugins version" +msgstr "Les opcions de depuració disponibles són: plugins version" msgid "No such command (in this context)." msgstr "L'ordre no existeix (en aquest context)." @@ -977,6 +980,9 @@ msgid "(none)" msgstr "(cap)" +#. XXX: The following expects that finch_notify_message gets called. This +#. * may not always happen, e.g. when another plugin sets its own +#. * notify_message. So tread carefully. msgid "URI" msgstr "URI" @@ -1554,9 +1560,15 @@ "\n" "S'està aconseguint un TinyURL..." -#, fuzzy +#, c-format +msgid "TinyURL for above: %s" +msgstr "Fes un TinyURL d'això d'aquí dalt: %s" + +msgid "Please wait while TinyURL fetches a shorter URL ..." +msgstr "Espreu mentre TinyURL obté una URL més curta..." + msgid "Only create TinyURL for URLs of this length or greater" -msgstr "Crea TinyURL per a URL així de llargues o més" +msgstr "Només crea TinyURL per a URL així de llargues o més" msgid "TinyURL (or other) address prefix" msgstr "Prefix de l'adreça TinyURL (o altra)" @@ -1567,10 +1579,9 @@ msgid "TinyURL plugin" msgstr "Connector TinyURL" -#, fuzzy msgid "When receiving a message with URL(s), use TinyURL for easier copying" msgstr "" -"Quan rebeu missagtes amb URL, feu servir TinyURL per a copiar més fàcilment" +"En rebre missagtes amb URL, s'empra TinyURL perquè sigui més fàcil copiar" msgid "Online" msgstr "En línia" @@ -1676,27 +1687,25 @@ msgid "buddy list" msgstr "llista d'amics" -#, fuzzy msgid "The certificate is self-signed and cannot be automatically checked." -msgstr "" -"No es pot comprovar el certificat que presenta «%s» atès que està auto-signat." - -#, fuzzy -msgid "The root certificate this one claims to be issued by is unknown." -msgstr "El Pidgin no coneix el certificat arrel d'aquest certificat." - -#, fuzzy +msgstr "No es pot comprovar el certificat atès que està auto-signat." + +msgid "" +"The certificate is not trusted because no certificate that can verify it is " +"currently trusted." +msgstr "" +"No es pot confiar en el certificat atès que no hi hi ha cap altre certificat " +"de confiança que el pugui verificar." + msgid "The certificate is not valid yet." -msgstr "La cadena de certificació que presenta %s no és vàlida." - -#, fuzzy +msgstr "El certificat encara no és vàlid." + msgid "The certificate has expired and should not be considered valid." -msgstr "La cadena de certificació que presenta %s no és vàlida." +msgstr "El certificat ha expirat i no s'hauria de considerar vàlid." #. Translators: "domain" refers to a DNS domain (e.g. talk.google.com) -#, fuzzy msgid "The certificate presented is not issued to this domain." -msgstr "La cadena de certificació que presenta %s no és vàlida." +msgstr "El certificat que s'ha presentat no ha estat emès per a aquest domini." msgid "" "You have no database of root certificates, so this certificate cannot be " @@ -1705,17 +1714,14 @@ "Aquest certificat no es pot validar perquè no teniu cap base de dades de " "certificats arrel." -#, fuzzy msgid "The certificate chain presented is invalid." -msgstr "La cadena de certificació que presenta %s no és vàlida." - -#, fuzzy +msgstr "La cadena de certificació que s'ha presentat no és vàlida." + msgid "The certificate has been revoked." -msgstr "Ha finalitzat la trucada." - -#, fuzzy +msgstr "El certificat ha estat revocat." + msgid "An unknown certificate error occurred." -msgstr "Hi ha hagut un error de connexió desconegut: %s." +msgstr "S'ha produït un error desconegut en el certificat." msgid "(DOES NOT MATCH)" msgstr "(NO COINCIDEIX)" @@ -1760,26 +1766,25 @@ msgid "_View Certificate..." msgstr "_Mostra el certificat..." -#, fuzzy, c-format +#, c-format msgid "The certificate for %s could not be validated." -msgstr "La cadena de certificació que presenta %s no és vàlida." +msgstr "No s'ha pogut validar el certificat de %s." # Títol de finestra (josep) #. TODO: Probably wrong. msgid "SSL Certificate Error" msgstr "Error en el certificat SSL" -#, fuzzy msgid "Unable to validate certificate" -msgstr "No s'ha pogut autenticar: %s" - -#, fuzzy, c-format +msgstr "No s'ha pogut certificar" + +#, c-format msgid "" "The certificate claims to be from \"%s\" instead. This could mean that you " "are not connecting to the service you believe you are." msgstr "" -"El certificat de «%s» sembla indicar que és de «%s». Això podria voler dir que " -"us esteu connectant a un servei diferent del que us penseu." +"El certificat indica que és de «%s». Això podria voler dir que us esteu " +"connectant a un servei diferent del que us penseu." #. Make messages #, c-format @@ -1927,6 +1932,10 @@ msgstr "El procés resoledor ha acabat sense respondre la nostra sol·licitud" #, c-format +msgid "Error converting %s to punycode: %d" +msgstr "S'ha produït un error en convertir %s a punycode: %d" + +#, c-format msgid "Thread creation failure: %s" msgstr "S'ha produït un error en crear un fil: %s" @@ -2020,18 +2029,18 @@ msgid "File transfer complete" msgstr "S'ha completat la transferència del fitxer" -#, fuzzy, c-format +#, c-format msgid "You cancelled the transfer of %s" msgstr "Heu cancel·lat la transferència de %s" msgid "File transfer cancelled" msgstr "S'ha cancel·lat la transferència del fitxer" -#, fuzzy, c-format +#, c-format msgid "%s cancelled the transfer of %s" msgstr "%s ha cancel·lat la transferència de %s" -#, fuzzy, c-format +#, c-format msgid "%s cancelled the file transfer" msgstr "%s ha cancel·lat la transferència del fitxer" @@ -2224,28 +2233,30 @@ "No codecs found. Install some GStreamer codecs found in GStreamer plugins " "packages." msgstr "" +"No s'ha trobat cap còdec. Instal·leu els còdecs del GStreamer que podeu " +"trobar en els paquests de connectors del GStreamer." msgid "" "No codecs left. Your codec preferences in fs-codecs.conf are too strict." msgstr "" - -#, fuzzy +"No hi ha cap més còdec. Les preferències dels còdecs al fitxer fs-codecs." +"conf són massa estrictes." + msgid "A non-recoverable Farsight2 error has occurred." -msgstr "Hi ha hagut un error de connexió desconegut: %s." - -#, fuzzy -msgid "Conference error." -msgstr "Conferència tancada" - -msgid "Error with your microphone." -msgstr "" - -msgid "Error with your webcam." -msgstr "" - -#, fuzzy, c-format +msgstr "S'ha produït un error no recuperable del Farsight2." + +msgid "Conference error" +msgstr "Error en la conferència" + +msgid "Error with your microphone" +msgstr "S'ha produït un error amb el micròfon" + +msgid "Error with your webcam" +msgstr "S'ha produït un error amb la càmera web" + +#, c-format msgid "Error creating session: %s" -msgstr "S'ha produït un error en crear la connexió" +msgstr "S'ha produït un error en crear la sessió: %s" msgid "Error creating conference." msgstr "S'ha produït un error en crear la conferència." @@ -2511,16 +2522,15 @@ msgid "Test plugin IPC support, as a server. This registers the IPC commands." msgstr "Connector de proves per a servidor d'IPC, que registra les ordres IPC." -#, fuzzy msgid "Hide Joins/Parts" -msgstr "Oculta els errors en entrar" +msgstr "Oculta en entrar/sortir" #. Translators: Followed by an input request a number of people msgid "For rooms with more than this many people" -msgstr "" +msgstr "Per sales amb més persones que" msgid "If user has not spoken in this many minutes" -msgstr "" +msgstr "Si l'usuari no ha parlat en" msgid "Apply hiding rules to buddies" msgstr "Aplica les normes d'ocultació als amics" @@ -3973,11 +3983,11 @@ msgid "Logo" msgstr "Logotip" -#, fuzzy, c-format +#, c-format msgid "" "%s will no longer be able to see your status updates. Do you want to " "continue?" -msgstr "Esteu segur que voleu suprimir %s de la llista d'amics?" +msgstr "%s no podrà veure l'actualització del vostre estat. Voleu continuar?" msgid "Cancel Presence Notification" msgstr "Cancel·la la notificació de presència" @@ -3996,6 +4006,9 @@ msgid "Unsubscribe" msgstr "Cancel·la la subscripció" +msgid "Initiate _Chat" +msgstr "Inicia un _xat" + msgid "Log In" msgstr "Connecta" @@ -4580,7 +4593,7 @@ msgid "configure: Configure a chat room." msgstr "configure: configura la sala de xat." -msgid "part [room]: Leave the room." +msgid "part [message]: Leave the room." msgstr "part [sala]: surt de la sala." msgid "register: Register with a chat room." @@ -4599,13 +4612,12 @@ "affiliate <owner|admin|member|outcast|none> [sobrenom1] " "[sobrenom2] ...: obtén els usuaris amb una afiliació, o els l'estableix." -#, fuzzy msgid "" "role <moderator|participant|visitor|none> [nick1] [nick2] ...: Get the " "users with a role or set users' role with the room." msgstr "" -"role <usuari> <moderator|participant|visitor|none> [sobrenom1] " -"[sobrenom2] ...: obtén els usuaris amb el rol especificat, o els l'estableix." +"role <moderator|participant|visitor|none> [sobrenom1] [sobrenom2] ...: " +"obtén els usuaris amb el rol especificat, o els l'estableix." msgid "invite <user> [message]: Invite a user to the room." msgstr "invite <usuari> [sala]: convida un usuari a la sala." @@ -5025,7 +5037,6 @@ msgid "Not expected" msgstr "Inesperat" -#, fuzzy msgid "Friendly name is changing too rapidly" msgstr "El nom amistós canvia massa de pressa" @@ -5245,19 +5256,16 @@ msgid "Send to Mobile" msgstr "Envia a un mòbil" -msgid "Initiate _Chat" -msgstr "Inicia un _xat" - msgid "SSL support is needed for MSN. Please install a supported SSL library." msgstr "L'MSN necessita SSL, instal·leu alguna biblioteca d'SSL permesa." #, c-format msgid "" "Unable to add the buddy %s because the username is invalid. Usernames must " -"be a valid email address." +"be valid email addresses." msgstr "" "No s'ha pogut afegir l'amic %s perquè el nom d'usuari no és vàlid. Els noms " -"d'usuari han de ser adreces de correu vàlides." +"d'usuari han de ser adreces de correu electròniques vàlides." msgid "Unable to Add" msgstr "No s'ha pogut afegir" @@ -5597,10 +5605,10 @@ "%s ha sol·licitat poder veure la vostra càmera web, però això encara no està " "implementat." -#, fuzzy, c-format +#, c-format msgid "%s invited you to view his/her webcam, but this is not yet supported." msgstr "" -"%s ha sol·licitat poder veure la vostra càmera web, però això encara no està " +"%s us ha convidat a veure la seva càmera web, però això encara no està " "implementat." msgid "Away From Computer" @@ -6354,9 +6362,9 @@ msgstr "Port en el servidor" #. Note to translators: %s in this string is a URL -#, fuzzy, c-format +#, c-format msgid "Received unexpected response from %s" -msgstr "S'ha rebut una resposta inesperada de " +msgstr "S'ha rebut una resposta inesperada de %s" #. username connecting too frequently msgid "" @@ -6369,9 +6377,9 @@ #. Note to translators: The first %s is a URL, the second is an #. error message. -#, fuzzy, c-format +#, c-format msgid "Error requesting %s: %s" -msgstr "S'ha produït en sol·licitar " +msgstr "S'ha produït un error en sol·licitar %s: %s" msgid "AOL does not allow your screen name to authenticate here" msgstr "AOL no permet que us autentiqueu amb aquest nom d'usuari aquí" @@ -7149,9 +7157,8 @@ msgid "C_onnect" msgstr "C_onnecta" -#, fuzzy msgid "You closed the connection." -msgstr "El servidor ha tancat la connexió" +msgstr "Heu tancat la connexió." msgid "Get AIM Info" msgstr "Obtén informació de AIM" @@ -7163,9 +7170,8 @@ msgid "Get Status Msg" msgstr "Aconsegueix el missatge d'estat" -#, fuzzy msgid "End Direct IM Session" -msgstr "S'ha establert una connexió directa de MI" +msgstr "Finalitzar la sessió de MI directa" msgid "Direct IM" msgstr "MI directa" @@ -8007,7 +8013,7 @@ msgid "File Send" msgstr "S'ha enviat el fitxer" -#, fuzzy, c-format +#, c-format msgid "%d cancelled the transfer of %s" msgstr "%d ha cancel·lat la transferència de %s" @@ -9545,7 +9551,7 @@ msgstr "Bloca invitacions a conferències i sales de xat" msgid "Use account proxy for SSL connections" -msgstr "" +msgstr "Empra un compte per al servidor intermediàri per a connexions SSL" msgid "Chat room list URL" msgstr "URL de la llista de sales de xat" @@ -9653,26 +9659,26 @@ msgid "Ignore buddy?" msgstr "Voleu ignorar l'amic?" -#, fuzzy msgid "Invalid username or password" -msgstr "El sobrenom o la contrasenya no són correctes" - -#, fuzzy +msgstr "El sobrenom o la contrasenya no són vàlides" + msgid "" "Your account has been locked due to too many failed login attempts. Please " "try logging into the Yahoo! website." msgstr "" -"El compte està blocat perquè s'ha intentat entrar massa cops. Això es pot " -"solucionar entrant al web de Yahoo!" +"S'ha blocat el vostre compte perquè s'ha intentat entrar massa cops. Entreu " +"al web de Yahoo! per solucionar això." #, c-format msgid "Unknown error 52. Reconnecting should fix this." -msgstr "" +msgstr "Error desconegut 52. Es pot sol·lucionar connectant de nou." msgid "" "Error 1013: The username you have entered is invalid. The most common cause " "of this error is entering your email address instead of your Yahoo! ID." msgstr "" +"Error 1013: el nom d'usuari no és vàlid. Pot ser que hagueu introduït la " +"vostra adreça de correu en lloc del nom d'usuari de Yahoo!" #, c-format msgid "Unknown error number %d. Logging into the Yahoo! website may fix this." @@ -9764,6 +9770,16 @@ msgid "Open Inbox" msgstr "Obre la safata d'entrada" +msgid "Can't send SMS. Unable to obtain mobile carrier." +msgstr "" +"No es poden enviar SMS, no s'ha pogut obtenir l'operador de telefonia mòbil." + +msgid "Can't send SMS. Unknown mobile carrier." +msgstr "No es poden enviar SMS, no es coneix l'operador de telefona mòbil." + +msgid "Getting mobile carrier to send the SMS." +msgstr "S'està obtenint l'operador de telefonia mòbil per a poder enviar SMS." + #. Write a local message to this conversation showing that a request for a #. * Doodle session has been made #. @@ -10456,7 +10472,6 @@ msgid "Layout" msgstr "Format" -#, fuzzy msgid "The layout of icons, name, and status of the buddy list" msgstr "El format de les icones, el nom, i l'estat de la llista d'amics" @@ -10513,9 +10528,8 @@ #. Note to translators: These two strings refer to the font and color #. of a buddy list buddy when it is online -#, fuzzy msgid "Online Text" -msgstr "Text en línia" +msgstr "Text en estar en línia" msgid "The text information for when a buddy is online" msgstr "Text informatiu per quan un amic estigui en línia" @@ -10523,18 +10537,16 @@ #. Note to translators: These two strings refer to the font and color #. of a buddy list buddy when it is away msgid "Away Text" -msgstr "Text d'absència" +msgstr "Text en estar absent" msgid "The text information for when a buddy is away" msgstr "Text informatiu per quan un amic estigui absent" #. Note to translators: These two strings refer to the font and color #. of a buddy list buddy when it is offline -#, fuzzy msgid "Offline Text" -msgstr "Text de fora de línia" - -#, fuzzy +msgstr "Text fora de línia" + msgid "The text information for when a buddy is offline" msgstr "Text informatiu per quan un amic estigui fora de línia" @@ -10559,7 +10571,6 @@ msgid "Message (Nick Said) Text" msgstr "Text del missatge (on s'hi ha dit el sobrenom)" -#, fuzzy msgid "" "The text information for when a chat has an unread message that mentions " "your nickname" @@ -11005,9 +11016,8 @@ msgid "_Group:" msgstr "_Grup:" -#, fuzzy msgid "Auto_join when account connects." -msgstr "_Entra automàticament quant el compte estigui connectat." +msgstr "_Entra automàticament quant es connecti el compte." msgid "_Remain in chat after window is closed." msgstr "Co_ntinua al xat quan la finestra es tanqui." @@ -11121,9 +11131,8 @@ msgid "/Conversation/New Instant _Message..." msgstr "/Conversa/_Missatge instantani nou..." -#, fuzzy msgid "/Conversation/Join a _Chat..." -msgstr "/Conversa/Con_vida..." +msgstr "/Conversa/Entra a un _xat..." msgid "/Conversation/_Find..." msgstr "/Conversa/_Cerca..." @@ -11514,7 +11523,7 @@ msgstr "Estonià" msgid "Basque" -msgstr "" +msgstr "Basc" msgid "Persian" msgstr "Persa" @@ -11728,6 +11737,13 @@ "primary language is <b>English</b>. You are welcome to post in another " "language, but the responses may be less helpful.<br/><br/>" msgstr "" +"<font size=\"4\">Ajuda d'altres usuaris del Pidgin:</font> <a href=\"mailto:" +"support@pidgin.im\">support@pidgin.im</a><br/>Aquesta és una llista de " +"correu <b>pública</b>. (<a href=\"http://pidgin.im/pipermail/support/" +"\">arxiu</a>)<br/>No us podem ajudar amb connectors d'altres proveïdors.<br/" +">En aquesta llista s'hi empra principalment l'<b>anglès</b>. Podeu escriure-" +"hi en un altre idioma, però és possible que les respostes no siguin de gaire " +"ajuda.<br/><br/>" #, c-format msgid "" @@ -12299,45 +12315,48 @@ "Usage: %s [OPTION]...\n" "\n" msgstr "" - -#, fuzzy +"Forma d'ús: %s [OPCIÓ]...\n" +"\n" + msgid "DIR" -msgstr "IRC" +msgstr "DIR" msgid "use DIR for config files" -msgstr "" +msgstr "empra DIR per a fitxers de configuració" msgid "print debugging messages to stdout" -msgstr "" +msgstr "escriu missatges de depuració a la sortida estàndard" msgid "force online, regardless of network status" -msgstr "" +msgstr "força estar en línia, independentment de l'estat de la xarxa" msgid "display this help and exit" -msgstr "" +msgstr "mostra aquesta ajuda i surt" # FIXME: entrades/registres? -#, fuzzy msgid "allow multiple instances" -msgstr "Permet diverses entrades simultànies" +msgstr "permet diverses instàncies" msgid "don't automatically login" -msgstr "" +msgstr "no entra als comptes" msgid "NAME" -msgstr "" +msgstr "NOM" msgid "" "enable specified account(s) (optional argument NAME\n" " specifies account(s) to use, separated by commas.\n" " Without this only the first account will be enabled)." msgstr "" +"habilita els comptes especificats (l'argument opcional NAME especifica\n" +" els comptes a emprar, separats per comes. Sense això\n" +" només s'habilitarà el primer compte)." msgid "X display to use" -msgstr "" +msgstr "pantalla d'X a emprar" msgid "display the current version and exit" -msgstr "" +msgstr "mostra la versió actual i surt" # FIXME: backtrace -> traça (bug-buddy) ? #, c-format @@ -12394,7 +12413,7 @@ msgstr "%s vol iniciar una sessió de vídeo." msgid "Incoming Call" -msgstr "" +msgstr "Trucada entrant" msgid "_Pause" msgstr "_Pausa" @@ -12576,50 +12595,54 @@ msgid "Pounce Target" msgstr "Objectiu de l'avís" -#, c-format msgid "Started typing" msgstr "Hagi començat a escriure" -#, c-format msgid "Paused while typing" msgstr "S'aturi mentre tecleja" -#, c-format msgid "Signed on" msgstr "Es connecti" -#, c-format msgid "Returned from being idle" msgstr "Torna a estar actiu" -#, c-format msgid "Returned from being away" msgstr "Torni a estar present" -#, c-format msgid "Stopped typing" msgstr "Pari d'escriure" -#, c-format msgid "Signed off" msgstr "Es desconnecti" -#, c-format msgid "Became idle" msgstr "Passi a inactiu" -#, c-format msgid "Went away" msgstr "En estar absent" -#, c-format msgid "Sent a message" msgstr "Envia un missatge" -#, c-format msgid "Unknown.... Please report this!" msgstr "Esdeveniment d'avís desconegut, informeu-nos-en." +msgid "(Custom)" +msgstr "(Personalitzat)" + +msgid "(Default)" +msgstr "(Predeterminat)" + +msgid "The default Pidgin sound theme" +msgstr "El tema de sons predeterminat del pidgin" + +msgid "The default Pidgin buddy list theme" +msgstr "El tema per a la llista d'amics predeterminat del Pidgin" + +msgid "The default Pidgin status icon theme" +msgstr "El tema de les icones d'estat predeterminat del Pidgin" + msgid "Theme failed to unpack." msgstr "No s'ha pogut desempaquetar el tema." @@ -12764,14 +12787,16 @@ msgid "Cannot start browser configuration program." msgstr "No s'ha pogut iniciar el programa de configuració del navegador." -#, fuzzy msgid "Disabled" -msgstr "_Inhabilita" +msgstr "Inhabilitat" #, c-format msgid "Use _automatically detected IP address: %s" msgstr "Empra l'_adreça IP detectada automàticament: %s" +msgid "ST_UN server:" +msgstr "Servidor ST_UN:" + msgid "<span style=\"italic\">Example: stunserver.org</span>" msgstr "<span style=\"italic\">Exemple: stunserver.org</span>" @@ -12797,9 +12822,8 @@ msgid "Relay Server (TURN)" msgstr "Servidor repetidor (TURN)" -#, fuzzy msgid "_TURN server:" -msgstr "Servidor ST_UN:" +msgstr "Servidor _TURN:" msgid "Proxy Server & Browser" msgstr "Servidor intermediari i navegador" @@ -14372,35 +14396,29 @@ "Aquest connector permet a l'usuari personalitzar els formats de les marques " "horàries de les converses i dels registres." -#, fuzzy msgid "Audio" -msgstr "Auto" - -#, fuzzy +msgstr "Àudio" + msgid "Video" msgstr " Vídeo" msgid "Output" -msgstr "" - -#, fuzzy +msgstr "Sortida" + msgid "_Plugin" -msgstr "Connectors" - -#, fuzzy +msgstr "_Connectors" + msgid "_Device" -msgstr "Dispositiu" +msgstr "_Dispositiu" msgid "Input" -msgstr "" - -#, fuzzy +msgstr "Entrada" + msgid "P_lugin" -msgstr "Connectors" - -#, fuzzy +msgstr "C_onnectors" + msgid "D_evice" -msgstr "Dispositiu" +msgstr "D_ispositiu" #. *< magic #. *< major version @@ -14411,18 +14429,19 @@ #. *< dependencies #. *< priority #. *< id -#, fuzzy msgid "Voice/Video Settings" -msgstr "Edita els paràmetres" +msgstr "Configuració del so/vídeo" #. *< name #. *< version msgid "Configure your microphone and webcam." -msgstr "" +msgstr "Configureu el micròfon i la càmera web." #. *< summary msgid "Configure microphone and webcam settings for voice/video calls." msgstr "" +"Configureu els paràmetres del micròfon i la càmera web per a trucades de veu/" +"vídeo." msgid "Opacity:" msgstr "Opacitat:" @@ -14481,9 +14500,6 @@ "\n" "* Nota: aquest connector requereix Windows 2000 o superior." -msgid "GTK+ Runtime Version" -msgstr "Versió del mòdul d'execució de GTK+" - #. Autostart msgid "Startup" msgstr "Inicialització" @@ -14492,6 +14508,10 @@ msgid "_Start %s on Windows startup" msgstr "_Inicia el %s en iniciar Windows" +# FIXME: entrades/registres? +msgid "Allow multiple instances" +msgstr "Permet diverses instàncies" + msgid "_Dockable Buddy List" msgstr "Llista _d'amics acoblable" @@ -14553,6 +14573,9 @@ msgid "This plugin is useful for debbuging XMPP servers or clients." msgstr "Aquest connector és útil per a depurar servidors i clients XMPP." +#~ msgid "GTK+ Runtime Version" +#~ msgstr "Versió del mòdul d'execució de GTK+" + #~ msgid "Calling ... " #~ msgstr "S'està trucant..." @@ -14796,9 +14819,6 @@ #~ msgid "Could not write" #~ msgstr "No s'ha pogut escriure" -#~ msgid "Could not connect" -#~ msgstr "No s'ha pogut connectar" - #~ msgid "Could not create listen socket" #~ msgstr "No s'ha pogut crear el sòcol per a escoltar"
--- a/po/de.po Mon Nov 09 19:27:38 2009 +0000 +++ b/po/de.po Mon Nov 09 19:27:45 2009 +0000 @@ -11,8 +11,8 @@ 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 @@ 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 @@ 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 @@ 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 @@ "\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 @@ 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 @@ 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 @@ #, 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ "\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 @@ 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"
--- a/po/vi.po Mon Nov 09 19:27:38 2009 +0000 +++ b/po/vi.po Mon Nov 09 19:27:45 2009 +0000 @@ -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 @@ 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 @@ " -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 @@ 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 @@ #, 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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" - -msgid "Supported debug options are: version" -msgstr "Tùy chọn gỡ lỗi được hỗ trợ : phiên bản" +msgstr[0] "Danh sách %d người dùng:\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 @@ msgid "me <action>: Send an IRC style action to a buddy or chat." msgstr "" -"me <hành động>: Gởi một hoạt động kiểu IRC đến bạn thân hay chát." +"me <hành động>: Gởi một hoạt động kiểu IRC đến bạn chát hay chát." msgid "" "debug <option>: Send various debug information to the current " @@ -868,12 +870,11 @@ 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 @@ 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 "" - -#, fuzzy +"%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ợ." + 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 @@ 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 @@ 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 @@ 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 @@ #. 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ "\n" "Fetching TinyURL..." msgstr "" +"\n" +"Đang lấy TinyURL..." + +#, c-format +msgid "TinyURL for above: %s" +msgstr "" + +msgid "Please wait while TinyURL fetches a shorter URL ..." +msgstr "" msgid "Only create TinyURL for URLs of this length or greater" -msgstr "" +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 "" - -#, fuzzy +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 @@ 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 @@ 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 @@ 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." - -#, fuzzy +msgstr "Chứng nhận này tự ký thì không thể được tự động kiểm tra." + 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." - -#, fuzzy +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ó." + 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ệ." - -#, fuzzy +msgstr "Chứng nhận này chưa hợp lệ." + 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 @@ "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ệ." - -#, fuzzy +msgstr "Dãy chứng nhận đưa ra không phải hợp lệ." + 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ệ." - -#, fuzzy +msgstr "Chứng nhận đã bị hủy bỏ." + 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 @@ 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" - -#, fuzzy, c-format +msgstr "Không thể thẩm tra chứng nhận" + +#, 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 @@ 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 @@ #, 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 @@ 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" - -#, fuzzy, c-format +msgstr "Đã truyền hoàn toàn tập tin" + +#, 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" - -#, fuzzy, c-format +msgstr "%s đã thôi tiến trình truyền %s" + +#, 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 @@ "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 "" - -#, fuzzy +"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ế." + msgid "A non-recoverable Farsight2 error has occurred." -msgstr "Lỗi đăng nhập không rõ : %s." - -#, fuzzy -msgid "Conference error." -msgstr "Hội thảo bị đóng" - -msgid "Error with your microphone." -msgstr "" - -msgid "Error with your webcam." -msgstr "" - -#, fuzzy, c-format +msgstr "Gặp một lỗi Farsight2 từ đó không thể phục hồi." + +msgid "Conference error" +msgstr "Lỗi hội thảo" + +msgid "Error with your microphone" +msgstr "Gặp lỗi với cái máy vi âm" + +msgid "Error with your webcam" +msgstr "Gặp lỗi với cái máy ảnh Web" + +#, c-format msgid "Error creating session: %s" -msgstr "Lỗi khi tạo kết nối" - -#, fuzzy +msgstr "Gặp lỗi khi tạo buổi hợp: %s" + 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 @@ "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 @@ #. *< 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 @@ "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 @@ "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 @@ "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 @@ 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 @@ 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 @@ 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 @@ #. *< 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 @@ "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 @@ 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 @@ #. *< 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 @@ "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 @@ "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." - -#, fuzzy +"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." + 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 @@ 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 @@ 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" - -#, fuzzy, c-format +msgstr "Không thể tạo ổ cắm: %s" + +#, c-format msgid "Unable to bind socket to port: %s" -msgstr "Không thể đóng kết ổ cắm tới cổng" - -#, fuzzy, c-format +msgstr "Không thể đóng kết ổ cắm tới cổng: %s" + +#, 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 @@ 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ý." - -#, fuzzy +msgstr "Phải điền vào tất cả các trường đăng ký" + msgid "Passwords do not match" -msgstr "Hai mật khẩu không trùng." - -#, fuzzy +msgstr "Hai mật khẩu không trùng nhau" + 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 @@ msgstr "Nhập lại mật khẩu" msgid "Enter captcha text" -msgstr "" - -#, fuzzy +msgstr "Gõ chuỗi Captcha" + +# 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 @@ #, 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ #. #. 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 @@ 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" - -#, fuzzy, c-format +msgstr "Không thể kết nối: %s" + +#, 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 @@ 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 @@ 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 @@ #. 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" - -#, fuzzy +msgstr "Tên hiệu « %s » đang được dùng." + 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 @@ "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 <nick1> [nick2] ...: Remove channel operator status from " @@ -3788,15 +3779,12 @@ 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." - -#, fuzzy +"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." + 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 @@ msgid "Plaintext Authentication" msgstr "Xác thực nhập thô" -#, fuzzy msgid "SASL authentication failed" -msgstr "Không xác thực được" - -#, fuzzy +msgstr "Lỗi xác thực SASL" + 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 @@ 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 "" - -#, fuzzy +msgstr "Bộ quản lý kết nối BOSH đã chấm dứt buổi hợp của bạn." + msgid "No session ID given" -msgstr "Không nêu lý do." - -#, fuzzy +msgstr "Chưa đưa ra mã số buổi hợp" + msgid "Unsupported version of BOSH protocol" -msgstr "Phiên bản không được hỗ trợ" - -#, fuzzy +msgstr "Phiên bản giao thức BOSH không được hỗ trợ" + 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" - -#, fuzzy, c-format +msgstr "Không thể thiết lập kết nối đến máy phục vụ" + +#, 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" - -#, fuzzy +msgstr "Không thể thiết lập kết nối đến máy phục vụ : %s" + 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 @@ 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 @@ #, c-format msgid "%s ago" -msgstr "" - -#, fuzzy +msgstr "%s trước" + msgid "Logged Off" msgstr "Đã đăng xuất" @@ -3963,12 +3939,13 @@ 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 @@ msgid "Find Rooms" msgstr "Tìm phòng" -#, fuzzy msgid "Affiliations:" -msgstr "Bí danh:" - -#, fuzzy +msgstr "Nhập hội:" + msgid "No users found" -msgstr "Không tìm thấy người dùng tương ứng" - -#, fuzzy +msgstr "Không tìm thấy người dùng nào" + msgid "Roles:" -msgstr "Vị trí" - -#, fuzzy +msgstr "Vai trò :" + msgid "Ping timed out" msgstr "Quá hạn ping" @@ -4141,6 +4114,8 @@ "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 @@ 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 @@ 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 @@ 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." - -#, fuzzy, c-format +msgstr "Không thể kêu gọi vì không biết gì về %s." + +#, 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." - -#, fuzzy, c-format +msgstr "Không thể kêu gọi, vì %s có thể chưa đăng nhập." + +#, 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 @@ 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ệ" - -#, fuzzy, c-format +msgstr "Không thể khởi đầu phương tiện với %s, JID không hợp lệ" + +#, 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" - -#, fuzzy, c-format +msgstr "Không thể khởi đầu phương tiện với %s, người dùng chưa kết nối" + +#, 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" - -#, fuzzy +"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" + msgid "Media Initiation Failed" -msgstr "Lỗi đăng ký" - -#, fuzzy, c-format +msgstr "Lỗi khởi đầu phương tiện" + +#, 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 @@ msgid "ban <user> [reason]: Ban a user from the room." msgstr "ban <người_dùng> [lý_do]: Cấm một người dùng ra khỏi phòng." -#, fuzzy msgid "" "affiliate <owner|admin|member|outcast|none> [nick1] [nick2] ...: Get " "the users with an affiliation or set users' affiliation with the room." msgstr "" -"affiliate <người_dùng> <owner|admin|member|outcast|none>: Đặt tư " -"cách người dùng trong phòng\n" -" • owner\t\tngười sở hữu\n" +"affiliate <owner|admin|member|outcast|none> [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ó." - -#, fuzzy +" • member\t\tthành viên\n" +" • outcast\t\tngười bị ruồng bỏ\n" +" • none\t\tkhông có" + msgid "" "role <moderator|participant|visitor|none> [nick1] [nick2] ...: Get the " "users with a role or set users' role with the room." msgstr "" -"role <người_dùng> <moderator|participant|visitor|none>: Đặ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 <moderator|participant|visitor|none> [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 <user> [message]: Invite a user to the room." msgstr "invite <người_dùng> [thông_điệp]: Mời một người dùng vào phòng." @@ -4667,13 +4647,12 @@ 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 @@ msgid "_Accept Defaults" msgstr "Chấp nhận _mặc định" -#, fuzzy msgid "No reason" -msgstr "Không nêu lý do." - -#, fuzzy, c-format +msgstr "Không có lý do" + +#, c-format msgid "You have been kicked: (%s)" -msgstr "Bạn bị %s đá: (%s)" - -#, fuzzy, c-format +msgstr "Bạn bị đá: (%s)" + +#, c-format msgid "Kicked (%s)" -msgstr "Bị %s đá (%s)" - -#, fuzzy +msgstr "Bị đá (%s)" + msgid "An error occurred on the in-band bytestream transfer\n" -msgstr "Gặp lỗi trong khi mở tập tin." - -#, fuzzy +msgstr "Gặp lỗi trong khi truyền luồng byte bên trong băng\n" + 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 @@ msgstr "Không thể thêm « %s »." msgid "Buddy Add error" -msgstr "" - -#, fuzzy +msgstr "Lỗi thêm bạn chát" + 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 @@ "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 @@ "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 @@ 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 @@ 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 @@ msgid "MSN Error: %s\n" msgstr "Lỗi MSN: %s\n" -#, fuzzy msgid "Other Contacts" -msgstr "Liên lạc đã thích" - -#, fuzzy +msgstr "Liên lạc khác" + 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 "" - -#, fuzzy, c-format +"%s đã gửi một trích tiếng nói. <a href='audio://%s'>Bấm đây để phát nó</a>" + +#, 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ợ." - -#, fuzzy, c-format +msgstr "%s đã gửi một trích tiếng nói mà không lưu được" + +#, 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 @@ 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" - -#, fuzzy +msgstr "Chuỗi bị chặn cho %s" + 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 "" - -#, fuzzy +"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" + 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 "" - -#, fuzzy +msgstr "Chơi lượt" + msgid "Working" -msgstr "Việc làm" +msgstr "Đi làm" msgid "Has you" msgstr "Có bạn" @@ -5216,13 +5186,11 @@ msgid "Album" msgstr "Tập nhạc" -#, fuzzy msgid "Game Title" -msgstr "Tên điệu" - -#, fuzzy +msgstr "Tên trò chơi" + 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 @@ 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 @@ 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." -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ố." - -#, fuzzy +"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ệ. Mỗi tên người " +"dùng cũng phải là một địa chỉ thư điện tử hợp lệ." + msgid "Unable to Add" msgstr "Không thể thêm" @@ -5436,9 +5402,8 @@ "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 @@ 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 @@ 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" - -#, fuzzy +msgstr "Sổ địa chỉ của bạn còn thiếu những người dùng theo đây" + 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 @@ "Message was not sent because the system is unavailable. This normally " "happens when the user is blocked or does not exist." msgstr "" - -#, fuzzy +"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." + 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:" - -#, fuzzy +msgstr "Tin nhẳn chưa đi được vì các tin nhẳn được gửi quá nhanh." + 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õ :" - -#, fuzzy +msgstr "Tin nhẳn chưa đi được vì gặp một lỗi mã hoá không được nhận ra." + 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 @@ "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ụ." - -#, fuzzy +msgstr "Giao thức mình không được hỗ trợ bởi trình phục vụ." + msgid "Error parsing HTTP" -msgstr "Lỗi phân tích HTTP." - -#, fuzzy +msgstr "Gặp lỗi khi phân tích cú pháp của mã HTTP" + 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 @@ 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ợ." - -#, fuzzy, c-format +msgstr "%s yêu cầu xem máy ảnh Web của bạn, mà chưa được hỗ trợ." + +#, 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 @@ 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ỉ" - -#, fuzzy +msgstr "Xoá bạn chát khỏi Sổ địa chỉ ?" + 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 @@ 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 @@ "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 @@ 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 @@ "%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 @@ 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 @@ 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 @@ "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 @@ 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 @@ "%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 @@ 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 @@ #. 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 @@ 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 @@ 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 @@ 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 @@ "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 @@ #. 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 @@ msgstr "Mức cảnh báo" msgid "Buddy Comment" -msgstr "Chú thích bạn thân" - -#, fuzzy, c-format +msgstr "Chú thích bạn chát" + +#, 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" - -#, fuzzy, c-format +msgstr "Không thể kết nối tới máy phục vụ xác thực: %s" + +#, 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 @@ 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ố." - -#, fuzzy, c-format +"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ố." + +#, 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 @@ #. 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 @@ 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 @@ #, 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ msgid "Other" msgstr "Khác" -#, fuzzy msgid "Visible" -msgstr "Giấu mặt" +msgstr "Hiện rõ" msgid "Friend Only" -msgstr "" - -#, fuzzy +msgstr "Chỉ người bạn" + msgid "Private" -msgstr "Riêng tư" +msgstr "Riêng" msgid "QQ Number" msgstr "Số QQ" @@ -7355,9 +7293,8 @@ 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 @@ msgid "Personal Introduction" msgstr "Giới thiệu cá nhân" -#, fuzzy msgid "City/Area" -msgstr "T.P." - -#, fuzzy +msgstr "T.P./Vùng" + msgid "Publish Mobile" -msgstr "Điện thoại di động cá nhân" - -#, fuzzy +msgstr "Xuất số di động" + 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" - -#, fuzzy +msgstr "Tử vi" + msgid "Zodiac" -msgstr "Ký hiệu hoàng đạo" - -#, fuzzy +msgstr "Hoàng đạo" + msgid "Blood" -msgstr "Bị chặn" - -#, fuzzy +msgstr "Máu" + msgid "True" -msgstr "Kim Ngưu" - -#, fuzzy +msgstr "Đúng" + msgid "False" -msgstr "Bị lỗi" - -#, fuzzy +msgstr "Sai" + msgid "Modify Contact" -msgstr "Sửa tài khoản" - -#, fuzzy +msgstr "Sửa liên lạc" + msgid "Modify Address" -msgstr "Địa chỉ nhà" - -#, fuzzy +msgstr "Sửa địa chỉ" + msgid "Modify Extended Information" -msgstr "Sửa đổi thông tin của tôi" - -#, fuzzy +msgstr "Sửa thông tin mở rộng" + msgid "Modify Information" -msgstr "Sửa đổi thông tin của tôi" - -#, fuzzy +msgstr "Sửa thông tin" + msgid "Update" -msgstr "Cập nhật lần cuối" - -#, fuzzy +msgstr "Cập nhật" + 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 @@ 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 "" - -#, fuzzy +msgstr "Sửa đổi bản ghi nhớ của họ theo ý kiến của bạn" + msgid "_Modify" -msgstr "Sửa" - -#, fuzzy +msgstr "_Sửa" + msgid "Memo Modify" -msgstr "Sửa" - -#, fuzzy +msgstr "Sửa ghi nhớ" + 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 "" - -#, fuzzy, c-format +msgstr "Yêu cầu của bạn bị từ chối." + +#, c-format msgid "%u requires verification" -msgstr "Cần thiết sự cho phép" - -#, fuzzy +msgstr "%u cần thiết sự thẩm tra" + msgid "Add buddy question" -msgstr "Có thêm bạn thân vào danh sách của bạn không?" - -#, fuzzy +msgstr "Hỏi câu cho bạn chát" + 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..." - -#, fuzzy, c-format +msgstr "Tiếc là tôi quá bận." + +#, c-format msgid "%u needs authorization" -msgstr "Người dùng %d yêu cầu sự cho phép" - -#, fuzzy +msgstr "%u yêu cầu sự cho phép" + msgid "Add buddy authorize" -msgstr "Có thêm bạn thân vào danh sách của bạn không?" - -#, fuzzy +msgstr "Thêm sự cho phép bạn chát" + 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" - -#, fuzzy +msgstr "Bạn chát QQ" + msgid "Add buddy" -msgstr "Thêm bạn thân" - -#, fuzzy +msgstr "Thêm bạn chát" + msgid "Invalid QQ Number" -msgstr "Mặt QQ không hợp lệ" - -#, fuzzy +msgstr "Con số QQ không hợp lệ" + msgid "Failed sending authorize" -msgstr "Xin hãy cho phép tôi." - -#, fuzzy, c-format +msgstr "Lỗi gửi sự cho phép" + +#, c-format msgid "Failed removing buddy %u" -msgstr "Không gỡ bỏ được bạn thân" - -#, fuzzy, c-format +msgstr "Lỗi gỡ bỏ bạn chát %u" + +#, 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è." - -#, fuzzy +msgstr "Lỗi gỡ bỏ mình khỏi danh sách bạn chát của %d" + 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 @@ 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 @@ msgid "QQ Qun" msgstr "QQ Qun" -#, fuzzy msgid "Please enter Qun number" -msgstr "Hãy nhập tên mới cho %s" - -#, fuzzy +msgstr "Hãy gõ con số Qun" + 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" - -#, fuzzy +msgstr "Bạn chỉ có khả năng tìm kiếm QQ bên bỉ\n" + msgid "(Invalid UTF-8 string)" -msgstr "Thiết lập ủy nhiệm không hợp lệ" - -#, fuzzy +msgstr "(Chuỗi UTF-8 không hợp lệ)" + msgid "Not member" -msgstr "Tôi không phải là thành viên" - -#, fuzzy +msgstr "Không phải thành viên" + msgid "Member" -msgstr "Là thành viên từ" - -#, fuzzy +msgstr "Thành viên" + msgid "Requesting" -msgstr "Hộp thoại yêu cầu" - -# Tên của ứng dụng khách tin nhắn khác: đừng dịch. -#, fuzzy +msgstr " Đăng yêu cầu" + msgid "Admin" -msgstr "Adium" - -#, fuzzy +msgstr "Quản trị" + msgid "Notice" -msgstr "Ghi chú" - -#, fuzzy +msgstr "Thông báo" + 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" - -#, fuzzy +msgstr "Giới thiệu mình" + msgid "Category" -msgstr "Lỗi chát" - -#, fuzzy +msgstr "Loại" + 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" - -#, fuzzy +msgstr "Qun này không cho phép người khác tham gia" + 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" - -#, fuzzy +msgstr "Đã tham gia thành công Qun %s (%u)" + 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 "" - -#, fuzzy +msgstr "Tham gia Qun, Đáp ứng không rõ" + msgid "Quit Qun" -msgstr "QQ Qun" +msgstr "Thoát khỏi Qun" msgid "" "Note, if you are the creator, \n" @@ -7642,51 +7533,47 @@ "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..." - -#, fuzzy +msgstr "Tiếc là nhóm này quá đầy" + msgid "Successfully changed Qun members" -msgstr "Bạn đã sửa đổi thành công thành viên Qun" - -#, fuzzy +msgstr "Đã thay đổi thành công thành viên Qun" + 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" - -#, fuzzy, c-format +msgstr "%u đã yêu cầu tham gia Qun %u cho %s" + +#, c-format msgid "%u request to join Qun %u" -msgstr "Người dùng %d đã yêu cầu tham gia nhóm %d" - -#, fuzzy, c-format +msgstr "%u yêu cầu tham gia Qun %u" + +#, 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 "" - -#, fuzzy, c-format +msgstr "<b>Tham gia Qun %u được %u tán thành cho %s</b>" + +#, c-format msgid "<b>Removed buddy %u.</b>" -msgstr "Bỏ bạn chát" - -#, fuzzy, c-format +msgstr "<b>Đã gỡ bỏ bạn chát %u.</b>" + +#, 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 @@ msgstr "Cấp" msgid " VIP" -msgstr "" +msgstr " VIP" msgid " TCP" -msgstr "" - -#, fuzzy +msgstr " TCP" + msgid " FromMobile" -msgstr "Di động" - -#, fuzzy +msgstr " FromMobile" + msgid " BindMobile" -msgstr "Di động" - -#, fuzzy +msgstr " BindMobile" + msgid " Video" -msgstr "Ảnh động trực tiếp" - -#, fuzzy +msgstr " Phim" + 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..." - -#, fuzzy, c-format +msgstr "Chọn biểu tượng..." + +#, 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" - -#, fuzzy, c-format +msgstr "<b>Thời gian đăng nhập</b>: %d-%d-%d, %d:%d:%d<br>\n" + +#, 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" - -#, fuzzy, c-format +msgstr "<b>Tổng các bạn chát trực tuyến</b>: %d<br>\n" + +#, 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" - -#, fuzzy, c-format +msgstr "<b>Cập nhật cuối:</b>: %d-%d-%d, %d:%d:%d<br>\n" + +#, c-format msgid "<b>Server</b>: %s<br>\n" -msgstr "<b>IP máy phục vụ </b>: %s: %d<br>\n" - -#, fuzzy, c-format +msgstr "<b>Máy phục vụ</b>: %s<br>\n" + +#, 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" - -#, fuzzy, c-format +msgstr "<b>Chế độ kết nối</b>: %s<br>\n" + +#, c-format msgid "<b>My Internet IP</b>: %s:%d<br>\n" -msgstr "<b>Chế độ kết nối:</b> %s<br>\n" - -#, fuzzy, c-format +msgstr "<b>Địa chỉ IP Internet của mình</b>: %s:%d<br>\n" + +#, c-format msgid "<b>Sent</b>: %lu<br>\n" -msgstr "<b>IP máy phục vụ </b>: %s: %d<br>\n" - -#, fuzzy, c-format +msgstr "<b>Đã gửi</b>: %lu<br>\n" + +#, c-format msgid "<b>Resend</b>: %lu<br>\n" -msgstr "<b>Cập nhật cuối:</b> %s<br>\n" - -#, fuzzy, c-format +msgstr "<b>Gửi lại</b>: %lu<br>\n" + +#, c-format msgid "<b>Lost</b>: %lu<br>\n" -msgstr "<b>Cập nhật cuối:</b> %s<br>\n" - -#, fuzzy, c-format +msgstr "<b>Bị mất</b>: %lu<br>\n" + +#, c-format msgid "<b>Received</b>: %lu<br>\n" -msgstr "<b>IP máy phục vụ </b>: %s: %d<br>\n" - -#, fuzzy, c-format +msgstr "<b>Đã nhận</b>: %lu<br>\n" + +#, c-format msgid "<b>Received Duplicate</b>: %lu<br>\n" -msgstr "<b>IP công của tôi:</b> %s<br>\n" - -#, fuzzy, c-format +msgstr "<b>Đã nhận bản sao</b>: %lu<br>\n" + +#, 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" - -#, fuzzy, c-format +msgstr "<b>Thời gian</b>: %d-%d-%d, %d:%d:%d<br>\n" + +#, 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 "" - -#, fuzzy +msgstr "<p><b>Người đóng góp mã nguồn</b>:<br>\n" + msgid "<p><b>Lovely Patch Writers</b>:<br>\n" -msgstr "<b>Cập nhật cuối:</b> %s<br>\n" - -#, fuzzy +msgstr "<p><b>Người tạo đắp vá rất hữu ích</b>:<br>\n" + msgid "<p><b>Acknowledgement</b>:<br>\n" -msgstr "<b>IP máy phục vụ </b>: %s: %d<br>\n" - -#, fuzzy +msgstr "<p><b>Công trạng</b>:<br>\n" + 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 "" - -#, fuzzy, c-format +msgstr "<i>Mời bạn tham gia !</i> :)" + +#, c-format msgid "About OpenQ %s" -msgstr "Giới thiệu %s" - -#, fuzzy +msgstr "Giới thiệu OpenQ %s" + 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 "" - -#, fuzzy +msgstr "Cập nhật mọi QQ Qun" + msgid "About OpenQ" -msgstr "Giới thiệu %s" - -#, fuzzy +msgstr "Giới thiệu OpenQ" + msgid "Modify Buddy Memo" -msgstr "Địa chỉ nhà" +msgstr "Sửa ghi nhớ bạn chát" #. *< type #. *< ui_requirement @@ -7852,189 +7727,171 @@ #. *< version #. * summary #. * description -#, fuzzy msgid "QQ Protocol Plugin" -msgstr "Phần bổ sung\tgiao thức QQ" - -#, fuzzy +msgstr "Phần bổ sung giao thức QQ" + msgid "Auto" -msgstr "Tác giả" - -#, fuzzy +msgstr "Tự động" + 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 "" - -#, fuzzy +msgstr "QQ2008" + msgid "Connect by TCP" -msgstr "Kết nối bằng TCP" - -#, fuzzy +msgstr "Kết nối qua TCP" + msgid "Show server notice" -msgstr "Cổng máy phục vụ" - -#, fuzzy +msgstr "Hiện thông báo máy phục vụ" + 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 "" - -#, fuzzy +msgstr "Nhận được tin nhẳn thì cũng hiển thị phòng chát" + msgid "Keep alive interval (seconds)" -msgstr "Lỗi giữ cho kết nối hoạt động" - -#, fuzzy +msgstr "Khoảng giữ cho kết nối hoạt động (giây)" + msgid "Update interval (seconds)" -msgstr "Lỗi giữ cho kết nối hoạt động" - -#, fuzzy +msgstr "Khoảng cập nhật (giây)" + 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 "" - -#, fuzzy, c-format +msgstr "Lỗi yêu cầu hiệu bài, 0x%02X" + +#, 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 "" - -#, fuzzy +msgstr "Gặp mã đáp ứng không được nhận ra khi đăng nhập (0x%02X)" + msgid "Requesting captcha" -msgstr "Đang yêu cầu sự chú ý của %s..." - -#, fuzzy +msgstr "Đang yêu cầu Captcha" + msgid "Checking captcha" -msgstr "Đang yêu cầu sự chú ý của %s..." - -#, fuzzy +msgstr "Đang kiểm tra Captcha" + msgid "Failed captcha verification" -msgstr "Lỗi xác thực Yahoo" - -#, fuzzy +msgstr "Lỗi thẩm tra Captcha" + msgid "Captcha Image" -msgstr "Lưu ảnh" - -#, fuzzy +msgstr "Ảnh Captcha" + msgid "Enter code" -msgstr "Nhập mật khẩu" - -#, fuzzy +msgstr "Gõ mã" + msgid "QQ Captcha Verification" -msgstr "Thẩm tra chứng nhận SSL" - -#, fuzzy +msgstr "Thẩm tra Captcha QQ" + 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..." - -#, fuzzy +msgstr "Đang lấy máy phục vụ" + msgid "Requesting token" -msgstr "Yêu cầu bị từ chối" - -#, fuzzy +msgstr "Đang yêu cầu hiệu bài" + msgid "Unable to resolve hostname" -msgstr "Không thể kết nối đến máy phục vụ." - -#, fuzzy +msgstr "Không thể quyết định tên máy" + msgid "Invalid server or port" -msgstr "Lỗi không hợp lệ" - -#, fuzzy +msgstr "Sai máy phục vụ hay cổng" + msgid "Connecting to server" -msgstr "Đang kết nối tới máy phục vụ SILC" - -#, fuzzy +msgstr "Đang kết nối tới máy phục vụ" + msgid "QQ Error" -msgstr "Lỗi QQid" - -#, fuzzy, c-format +msgstr "Lỗi QQ" + +#, c-format msgid "" "Server News:\n" "%s\n" "%s\n" "%s" -msgstr "Chuyển tiếp máy phục vụ ICQ" - -#, fuzzy, c-format +msgstr "" +"Tin tức Máy phục vụ :\n" +"%s\n" +"%s\n" +"%s" + +#, c-format msgid "%s:%s" -msgstr "%s (%s)" - -#, fuzzy, c-format +msgstr "%s:%s" + +#, c-format msgid "From %s:" -msgstr "Từ" - -#, fuzzy, c-format +msgstr "Từ %s:" + +#, c-format msgid "" "Server notice From %s: \n" "%s" -msgstr "Hướng dẫn máy phục vụ : %s" - -#, fuzzy +msgstr "" +"Thông báo máy phục vụ Từ %s: \n" +"%s" + 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 "" - -#, fuzzy +"Lỗi đáp ứng %s(0x%02X)\n" +"Phòng %u, đáp ứng 0x%02X" + msgid "QQ Qun Command" -msgstr "Lệnh" - -#, fuzzy +msgstr "Lệnh QQ Qun" + msgid "Unable to decrypt login reply" -msgstr "Không thể lấy thông tin về máy phục vụ" - -#, fuzzy +msgstr "Không thể giải mật mã đáp ứng đăng nhập" + msgid "Unknown LOGIN CMD" -msgstr "Lý do không rõ" - -#, fuzzy +msgstr "Câu lệnh đăng nhập (LOGIN CMD) không rõ" + 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 @@ 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 @@ 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 @@ 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ớ" - -#, fuzzy +msgstr "Không đủ bộ nhớ" + 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 @@ 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 @@ 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" - -#, fuzzy, c-format +msgstr "Nguyễn Văn Không_tên" + +#, 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ụ" - -#, fuzzy +msgstr "Không nhận ra đáp ứng máy phục vụ" + 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 @@ 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 @@ #. *< 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 @@ 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 @@ 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 @@ #. *< 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 @@ 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 @@ 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" - -#, fuzzy +msgstr "Sai gõ tên người dùng hay mật khẩu" + 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ụ." - -#, fuzzy, c-format +msgstr "Nhận được đáp ứng HTTP bất thường từ máy phục vụ" + +#, c-format msgid "Lost connection with %s: %s" -msgstr "" -"Mất kết nối với %s:\n" -"%s" - -#, fuzzy, c-format +msgstr "Mất kết nối với %s: %s" + +#, 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ "<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" -"\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>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, 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" - -#, fuzzy +msgstr "Màu nền của danh sách bạn chát" + 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" - -#, fuzzy +msgstr "Chú giải Ngoại tuyến" + 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 "" - -#, fuzzy +"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" + 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 @@ 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 @@ 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 "" - -#, fuzzy +msgstr "Gọi thoại/_phim" + 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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" - -#, fuzzy +msgstr "/Cuộc thoại/_Phương tiện" + msgid "/Conversation/Media/_Audio Call" -msgstr "/Cuộc thoại/Nữ_a" - -#, fuzzy +msgstr "/Cuộc thoại/Phương tiện/Gọi th_oại" + msgid "/Conversation/Media/_Video Call" -msgstr "/Cuộc thoại/Nữ_a" - -#, fuzzy +msgstr "/Cuộc thoại/Phương tiện/Gọi _phim" + 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 @@ 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" - -#, fuzzy +msgstr "/Cuộc thoại/Phương tiện/Gọi thoại" + msgid "/Conversation/Media/Video Call" -msgstr "/Cuộc thoại/Xem bản ghi" - -#, fuzzy +msgstr "/Cuộc thoại/Phương tiện/Gọi phim" + 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 @@ msgstr "Lỗi nghiêm trọng" msgid "bug master" -msgstr "" - -#, fuzzy +msgstr "chủ lỗi" + 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ "<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 @@ "primary language is <b>English</b>. You are welcome to post in another " "language, but the responses may be less helpful.<br/><br/>" msgstr "" - -#, fuzzy, c-format +"<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/>" + +#, 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>" - -#, fuzzy, c-format +msgstr "<FONT SIZE=\"4\">Kênh IRC:</FONT> #pidgin on irc.freenode.net<BR><BR>" + +#, 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 @@ 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" - -#, fuzzy +msgstr "Màu siêu liên kết đã thăm" + 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 @@ 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 @@ "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 "" - -#, fuzzy +msgstr "hiển thị trợ giúp này, sau đó thoát" + 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 "" - -#, fuzzy, c-format +msgstr "hiển thị phiên bản hiện thời, sau đó thoát" + +#, 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 @@ "Đâ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 @@ "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 @@ #, 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 @@ 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 @@ 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 "" - -#, fuzzy +msgstr "Hủy" + 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 @@ 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 @@ msgid "Pounce Target" msgstr "Đích thông báo" -#, fuzzy, c-format +#, c-format msgid "Started typing" -msgstr "Bắt đầu gõ phím" - -#, fuzzy, c-format +msgstr "Đã bắt đầu gõ phím" + +#, c-format msgid "Paused while typing" -msgstr "Tạm dừng khi gõ phím" - -#, fuzzy, c-format +msgstr "Đã tạm dừng khi gõ phím" + +#, c-format msgid "Signed on" -msgstr "Đăng nhập" - -#, fuzzy, c-format +msgstr "Đã đăng nhập" + +#, c-format msgid "Returned from being idle" -msgstr "%s hoạt động trở lại từ trạng thái nghỉ (%s)" - -#, fuzzy, c-format +msgstr "Đã trở lại từ trạng thái nghỉ" + +#, c-format msgid "Returned from being away" -msgstr "Có mặt trở lại" - -#, fuzzy, c-format +msgstr "Đã có mặt trở lại" + +#, c-format msgid "Stopped typing" -msgstr "Dừng gõ phím" - -#, fuzzy, c-format +msgstr "Đã dừng gõ phím" + +#, c-format msgid "Signed off" -msgstr "Đăng xuất" - -#, fuzzy, c-format +msgstr "Đã đăng xuất" + +#, 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" - -#, fuzzy, c-format +msgstr "Đã vắng mặt" + +#, c-format msgid "Sent a message" -msgstr "Gửi tin nhẳn" - -#, fuzzy, c-format +msgstr "Đã gửi tin nhẳn" + +#, 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!" - -#, fuzzy +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" + msgid "Theme failed to unpack." -msgstr "Sắc thái hình cười không giải nén được." - -#, fuzzy +msgstr "Sắc thái không giải nén được." + msgid "Theme failed to load." -msgstr "Sắc thái hình cười không giải nén được." - -#, fuzzy +msgstr "Sắc thái không nạp được." + 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 @@ 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 @@ 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 @@ 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" - -#, fuzzy, c-format +msgstr "Bị tắt" + +#, 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 @@ #. TURN server msgid "Relay Server (TURN)" -msgstr "" - -#, fuzzy +msgstr "Máy phục vụ tiếp lại (TURN)" + msgid "_TURN server:" -msgstr "Máy phục vụ ST_UN:" +msgstr "Máy phục vụ _TURN:" msgid "Proxy Server & Browser" msgstr "Máy phục vụ ủy nhiệm và Trình duyệt" @@ -12871,7 +12684,7 @@ #. 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 @@ 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 @@ 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 @@ "_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:" - -#, fuzzy +msgstr "_Bật âm thanh:" + msgid "V_olume:" -msgstr "Âm lượng:" +msgstr "Â_m lượng:" msgid "Play" msgstr "Chơi" @@ -13034,7 +12844,7 @@ 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 @@ 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 @@ 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" - -#, fuzzy +msgstr "Bộ Quản lý Hình cười Riêng" + msgid "Select Buddy Icon" -msgstr "Chọn bạn chát" - -#, fuzzy +msgstr "Chọn biểu tượng bạn chát" + 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:" - -#, fuzzy +msgstr "Nhấn vào để thay đổi biểu tượng bạn chát cho tài khoản này." + 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 @@ 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 @@ 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 @@ 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 @@ msgid "_Copy Email Address" msgstr "_Chép địa chỉ thư" -#, fuzzy msgid "_Open File" -msgstr "Mở tập tin..." - -#, fuzzy +msgstr "_Mở tập tin" + 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" - -#, fuzzy +msgstr "_Phát âm thanh" + 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 @@ msgid "_Invite" msgstr "Mờ_i" -#, fuzzy msgid "_Modify..." -msgstr "_Sửa" - -#, fuzzy +msgstr "_Sửa..." + 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 @@ 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 @@ #. 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ụ" - -#, fuzzy +msgstr "Yêu cầu tên máy phục vụ" + msgid "Enter an XMPP Server" -msgstr "Nhập máy phục vụ hội thảo" - -#, fuzzy +msgstr "Nhập một máy phục vụ XMPP" + msgid "Select an XMPP server to query" -msgstr "Chọn một máy phục vụ hội thảo để hỏi" - -#, fuzzy +msgstr "Chọn một máy phục vụ XMPP để hỏi" + msgid "Find Services" -msgstr "Dịch vụ trực tuyến" - -#, fuzzy +msgstr "Tìm dịch vụ" + msgid "Add to Buddy List" -msgstr "Gửi danh sách bạn bè" - -#, fuzzy +msgstr "Thêm vào danh sách bạn chát" + msgid "Gateway" -msgstr "Đi vắng" - -#, fuzzy +msgstr "Cổng ra" + msgid "Directory" -msgstr "Thư mục chứa sổ theo dõi" - -#, fuzzy +msgstr "Thư mục" + msgid "PubSub Collection" -msgstr "Chọn âm thanh" - -#, fuzzy +msgstr "Tập hợp PubSub" + msgid "PubSub Leaf" -msgstr "Dịch vụ PubSub" - -#, fuzzy +msgstr "PubSub Leaf" + 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ụ" - -#, fuzzy +msgstr "Phát hiện Dịch vụ" + msgid "_Browse" -msgstr "Trình _duyệt:" - -#, fuzzy +msgstr "_Duyệt" + msgid "Server does not exist" -msgstr "Người dùng đó không tồn tại." - -#, fuzzy +msgstr "Máy phục vụ không tồn tại" + 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" - -#, fuzzy +msgstr "Máy phục vụ không hỗ trợ chức năng phát hiện dịch vụ" + 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 "" - -#, fuzzy +msgstr "Cho phép duyệt qua và đăng ký các dịch vụ." + 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 @@ 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 @@ 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 @@ 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 @@ 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õ" - -#, fuzzy +msgstr "Tắt chuỗi thông báo đang gõ" + msgid "GTK+ Theme Control Settings" -msgstr "Điều khiển sắc thái GTK+ Pidgin" - -#, fuzzy +msgstr "Thiết lập Điều khiển Sắc thái GTK+" + 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 @@ #, 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" - -#, fuzzy +msgstr "Về sau" + msgid "Download Now" -msgstr "Người dùng trên %s: %s" +msgstr "Tải về ngay" #. *< type #. *< ui_requirement @@ -14208,12 +13979,11 @@ 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 @@ 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" - -#, fuzzy +msgstr "Mới đăng nhập" + 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 "" - -#, fuzzy +"Biểu tượng cho Liên lạc\n" +"Biểu tượng cho Người lạ" + msgid "Icon for Chat" -msgstr "Tham gia Chat" - -#, fuzzy +msgstr "Biểu tượng cho Chát" + msgid "Ignored" -msgstr "Lờ" - -#, fuzzy +msgstr "Bị lờ" + msgid "Founder" -msgstr "To hơn" - -# Tên trình duyệt Web +msgstr "Người sáng lập" + #. 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 "" - -#, fuzzy +msgstr "Nửa thao tác viên" + msgid "Authorization dialog" -msgstr "Cho phép" - -#, fuzzy +msgstr "Hộp thoại cho phép" + msgid "Error dialog" -msgstr "Lỗi " - -#, fuzzy +msgstr "Hộp thoại lỗi " + msgid "Information dialog" -msgstr "Thông tin" +msgstr "Hộp thoại thông tin" msgid "Mail dialog" -msgstr "" - -#, fuzzy +msgstr "Hộp thoại thư tín" + msgid "Question dialog" -msgstr "Hộp thoại yêu cầu" - -#, fuzzy +msgstr "Hộp thoại câu hỏi" + 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 "" - -#, fuzzy +msgstr "Hộp thoại này có loại nào?" + msgid "Status Icons" -msgstr "Trạng thái cho %s" - -#, fuzzy +msgstr "Biểu tượng Trạng thái" + msgid "Chatroom Emblems" -msgstr "Miền địa phương phòng chát" - -#, fuzzy +msgstr "Hình tượng phòng chát" + msgid "Dialog Icons" -msgstr "Lưu biểu tượng" - -#, fuzzy +msgstr "Biểu tượng Hộp thoại" + msgid "Pidgin Icon Theme Editor" -msgstr "Điều khiển sắc thái GTK+ Pidgin" - -#, fuzzy +msgstr "Bộ Sửa Sắc thái Biểu tượng Pidgin" + msgid "Contact" -msgstr "Thông tin liên lạc" - -#, fuzzy +msgstr "Liên lạc" + msgid "Pidgin Buddylist Theme Editor" -msgstr "Danh sách bạn bè" - -#, fuzzy +msgstr "Bộ Sửa Sắc thái Danh sách Bạn chát Pidgin" + 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 @@ #. *< 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 @@ "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ả" - -#, fuzzy +msgstr "Âm thanh" + msgid "Video" -msgstr "Ảnh động trực tiếp" +msgstr "Phim" msgid "Output" -msgstr "" - -#, fuzzy +msgstr "Kết xuất" + msgid "_Plugin" -msgstr "Phần bổ sung" - -#, fuzzy +msgstr "_Phần bổ sung" + msgid "_Device" -msgstr "Thiết bị" +msgstr "_Thiết bị" msgid "Input" -msgstr "" - -#, fuzzy +msgstr "Đầu vào" + msgid "P_lugin" -msgstr "Phần bổ sung" - -#, fuzzy +msgstr "Phần bổ _sung" + msgid "D_evice" -msgstr "Thiết bị" +msgstr "Thiết _bị" #. *< magic #. *< major version @@ -14501,18 +14246,19 @@ #. *< 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 @@ "\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 @@ 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 @@ 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 @@ 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"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/ca-certs/Entrust.net_Secure_Server_CA.pem Mon Nov 09 19:27:45 2009 +0000 @@ -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-----
--- a/share/ca-certs/Makefile.am Mon Nov 09 19:27:38 2009 +0000 +++ b/share/ca-certs/Makefile.am Mon Nov 09 19:27:45 2009 +0000 @@ -2,6 +2,7 @@ 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 \