Wed, 12 Sep 2007 19:11:38 +0000
propagate from branch 'im.pidgin.pidgin' (head 447ebf410d618d48003894dc348f9f0e1616e1e7)
to branch 'org.maemo.garage.pidgin.pidgin.dialog-transience' (head 3f641641f3d6a62f9e576bca75c065e619a97a29)
--- a/.mtn-ignore Sun Aug 19 13:40:34 2007 +0000 +++ b/.mtn-ignore Wed Sep 12 19:11:38 2007 +0000 @@ -1,3 +1,4 @@ +(.*/)?\.svn .*/?Makefile(\.in)?$ (.*/)?TAGS$ .*/?.*\.pc$ @@ -33,7 +34,7 @@ pidgin-.*.tar.gz pidgin-.*.tar.bz2 pidgin/pidgin$ -pidgin/pixmaps/emotes/default/22/theme +pidgin/pixmaps/emotes/default/24/theme pidgin/pixmaps/emotes/none/theme pidgin/plugins/musicmessaging/music-messaging-bindings.c pidgin/plugins/perl/common/Makefile.PL$
--- a/AUTHORS Sun Aug 19 13:40:34 2007 +0000 +++ b/AUTHORS Wed Sep 12 19:11:38 2007 +0000 @@ -22,7 +22,7 @@ Ka-Hing Cheung - Developer Sadrul Habib Chowdhury - Developer Mark 'KingAnt' Doliner - Developer -Christian 'ChipX86' Hammond - Developer & Webmaster +Casey Harkins - Developer Gary 'grim' Kramlich - Developer Richard 'rlaager' Laager - Developer Richard 'wabz' Nelson - Developer @@ -31,21 +31,16 @@ Etan 'deryni' Reisner - Developer Tim 'marv' Ringenbach - Developer Luke 'LSchiere' Schierer - Support -Megan 'Cae' Schneider (support/QA) +Megan 'Cae' Schneider - support/QA Evan Schoenberg - Developer +Kevin 'SimGuy' Stange - Developer & Webmaster Stu 'nosnilmot' Tomlinson - Developer Nathan 'faceprint' Walp - Developer Crazy Patch Writers: ------------------- John 'rekkanoryo' Bailey -Felipe 'shx' Contreras -Decklin Foster -Casey Harkins Peter 'Bleeter' Lawler -Robert 'Robot101' McQueen -Benjamin Miller -Kevin 'SimGuy' Stange Retired Developers: ------------------ @@ -53,11 +48,19 @@ Jim Duchek <jim@linuxpimps.com> - maintainer Rob Flynn <gaim@robflynn.com> - maintainer Adam Fritzler - libfaim maintainer +Christian 'ChipX86' Hammond - Developer & Webmaster Syd Logan - hacker and designated driver [lazy bum] Jim Seymour - XMPP developer Mark Spencer <markster@marko.net> - original author Eric Warmenhoven <eric@warmenhoven.org> - lead developer +Retired Crazy Patch Writers: +--------------------------- +Felipe 'shx' Contreras +Decklin Foster +Robert 'Robot101' McQueen +Benjamin Miller + Artists: ------- Hylke Bons - Icons
--- a/COPYING Sun Aug 19 13:40:34 2007 +0000 +++ b/COPYING Wed Sep 12 19:11:38 2007 +0000 @@ -2,7 +2,7 @@ Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. - 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. @@ -305,7 +305,7 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA Also add information on how to contact you by electronic and paper mail.
--- a/COPYRIGHT Sun Aug 19 13:40:34 2007 +0000 +++ b/COPYRIGHT Wed Sep 12 19:11:38 2007 +0000 @@ -156,12 +156,14 @@ Casey Harkins Andy Harrison Andrew Hart (arhart) +Anders Hasselqvist Rene Hausleitner Will Hawkins G. Sumner Hayes Michael R. Head Nick Hebner Mike Heffner +Justin Heiner Benjamin Herrenschmidt Fernando Herrera hjheins @@ -214,6 +216,7 @@ Nicolas Lichtmaier Wesley Lin Artem Litvinovich +Josh Littlefield Syd Logan Lokheed Norberto Lopes @@ -304,6 +307,7 @@ Bob Rossi Jason Roth Jean-Francois Roy +Peter Ruibal Sam S. Pradyumna Sampath Arvind Samptur @@ -344,6 +348,7 @@ Sony Computer Entertainment America, Inc. Andy Spencer Mark Spencer +Peter Speybrouck Lex Spoon Chris Stafford Kevin Stange @@ -370,6 +375,7 @@ Warren Togami Stu Tomlinson Bill Tompkins +Gal Topper Chris Toshok Ken Tossell Tom Tromey
--- a/ChangeLog Sun Aug 19 13:40:34 2007 +0000 +++ b/ChangeLog Wed Sep 12 19:11:38 2007 +0000 @@ -1,6 +1,21 @@ Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul -version 2.1.1 (08/17/2007): +version 2.2.0: + Libpurple: + * New protocol plugin: MySpaceIM (Jeff Connelly, Google Summer of + Code) + + Pidgin: + * Insert Horizontal Rules and Strikethrough text from toolbar + * Option to show protocol icons in the buddy list, from the + Buddies > Show menu (Justin Heiner) + * Ability to build with native, non-X11 GTK+ on OSX (Anders + Hasselqvist) + + Finch: + * Per-conversation mute and logging options (accessible from the menu) + +version 2.1.1 (08/20/2007): Yahoo: * Added an account action to open your inbox in the yahoo prpl. * Added support for Unicode status messages in Yahoo.
--- a/ChangeLog.API Sun Aug 19 13:40:34 2007 +0000 +++ b/ChangeLog.API Wed Sep 12 19:11:38 2007 +0000 @@ -1,6 +1,57 @@ Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul -Version 2.1.1 (08/17/2007): +Version 2.2.0 (??/??/????): + libpurple: + Added: + * PURPLE_MESSAGE_INVISIBLE flag, which can be used by + purple_conv_im_send_with_flags to send a message, but not display it + in the conversation + * serv_send_attention(), serv_got_attention(), as well as send_attention + and attention_types in PurplePluginProtocolInfo. This new API is used + for zapping in MySpaceIM, buzzing in Yahoo, and nudging in MSN. + * PurpleConvMessage structure to represent a message in a + conversation. purple_conversation_message_get_sender, + purple_conversation_message_get_message, + purple_conversation_message_get_flags and + purple_conversation_message_get_timestamp to get information about a + PurpleConvMessage. + * purple_conversation_get_message_history() to retrieve a list of + PurpleConvMessage's in a conversation, and + purple_conversation_clear_message_history to clear the history. + + Changed: + * purple_prefs_load is now called within purple_prefs_init. + The UI no longer needs to call it. + * writing-im-msg now receives the conversation name as the who + argument if the caller of purple_conversation_write didn't + provide a value for who. + + Pidgin: + Added: + * pidgin_set_accessible_relations, sets up label-for and labelled-by + ATK relations (broken out from pidgin_set_accessible_label) + * pidgin_conv_attach_to_conversation, to reattach the Pidgin UI to a + conversation + * conversation-hiding and conversation-displayed signals. + + Changed: + * pidgin_conversations_fill_menu now also adds a separator and a 'Show + All' item if there are more than one conversations in the list. + + Finch: + Added: + * finch_sound_is_enabled + * The reserved field in the FinchConv is now used to store information + about the conversation (using FinchConversationFlag) + * finch_account_dialog_show + + libgnt: + * gnt_slider_set_small_step, gnt_slider_set_large_step to allow more + fine tuned updates of a GntSlider + * gnt_util_parse_xhtml_to_textview to parse XHTML strings in a + GntTextView (this works only if libxml2 is available) + +Version 2.1.1 (08/20/2007): libpurple: Changed: * PurpleAccountUiOps.request_authorize's authorize_cb and
--- a/ChangeLog.win32 Sun Aug 19 13:40:34 2007 +0000 +++ b/ChangeLog.win32 Wed Sep 12 19:11:38 2007 +0000 @@ -1,5 +1,9 @@ -version 2.1.1 (08/17/2007): - * No changes +version 2.2.0 (??/??/2007): + * Updated gtkspell to 2.0.11 + * Upgrade SILC to use the 1.1.2 toolkit + +version 2.1.1 (08/20/2007): + * No changes version 2.1.0 (7/28/2007): * Updated launcher application (pidgin.exe) to support portable mode
--- a/Makefile.mingw Sun Aug 19 13:40:34 2007 +0000 +++ b/Makefile.mingw Wed Sep 12 19:11:38 2007 +0000 @@ -51,6 +51,7 @@ silc.dll \ silcclient.dll \ softokn3.dll \ + smime3.dll \ ssl3.dll #build an expression for `find` to use to ignore the above files
--- a/NEWS Sun Aug 19 13:40:34 2007 +0000 +++ b/NEWS Wed Sep 12 19:11:38 2007 +0000 @@ -1,6 +1,6 @@ Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul -2.1.1 (8/17/2007): +2.1.1 (8/20/2007): Sean: Continuing our schedule of frequent releases, Pidgin 2.1.1 is out. In it, we've addressed a lot of UI issues from our experimental new changes introduced in 2.1.0, and gave a lot of
--- a/configure.ac Sun Aug 19 13:40:34 2007 +0000 +++ b/configure.ac Wed Sep 12 19:11:38 2007 +0000 @@ -43,18 +43,18 @@ # # Make sure to update finch/libgnt/configure.ac with libgnt version changes. # -m4_define([purple_lt_current], [1]) +m4_define([purple_lt_current], [2]) m4_define([purple_major_version], [2]) -m4_define([purple_minor_version], [1]) -m4_define([purple_micro_version], [1]) +m4_define([purple_minor_version], [2]) +m4_define([purple_micro_version], [0]) m4_define([purple_version_suffix], [devel]) m4_define([purple_version], [purple_major_version.purple_minor_version.purple_micro_version]) m4_define([purple_display_version], purple_version[]m4_ifdef([purple_version_suffix],[purple_version_suffix])) -m4_define([gnt_lt_current], [1]) +m4_define([gnt_lt_current], [2]) m4_define([gnt_major_version], [2]) -m4_define([gnt_minor_version], [1]) +m4_define([gnt_minor_version], [2]) m4_define([gnt_micro_version], [0]) m4_define([gnt_version_suffix], [devel]) m4_define([gnt_version], @@ -136,7 +136,7 @@ ;; esac -ALL_LINGUAS="af am ar az bg bn bs ca ca@valencia cs da de dz el en_AU en_CA en_GB eo es et eu fa fi fr gl gu he hi hu id it ja ka kn ko ku lt mk my_MM nb ne nl nn pa pl pt_BR pt ps ro ru sk sl sq sr sr@Latn sv ta te th tr uk vi xh zh_CN zh_HK zh_TW" +ALL_LINGUAS="af am ar az be@latin bg bn bs ca ca@valencia cs da de dz el en_AU en_CA en_GB eo es et eu fa fi fr gl gu he hi hu id it ja ka kn ko ku lt mk my_MM nb ne nl nn pa pl pt_BR pt ps ro ru sk sl sq sr sr@Latn sv ta te th tr uk vi xh zh_CN zh_HK zh_TW" AM_GLIB_GNU_GETTEXT dnl If we don't have msgfmt, then po/ is going to fail -- ensure that @@ -275,6 +275,8 @@ AC_SUBST(GLIB_CFLAGS) AC_SUBST(GLIB_LIBS) +AC_ARG_WITH(x, [], + with_x="$withval", with_x="yes") AC_ARG_ENABLE(gtkui, [AC_HELP_STRING([--disable-gtkui], [compile without GTK+ user interface])], enable_gtkui="$enableval", enable_gtkui="yes") @@ -309,7 +311,10 @@ [AC_HELP_STRING([--disable-cap], [compile without Contact Availability Prediction plugin])], enable_cap="$enableval", enable_cap="yes") - +AC_ARG_ENABLE(gestures, + [AC_HELP_STRING([--disable-gestures], + [compile without the gestures plugin])], + enable_gestures="$enableval", enable_gestures="yes") AC_PATH_XTRA # We can't assume that $x_libraries will be set, because autoconf does not @@ -343,50 +348,79 @@ AC_DEFINE(HAVE_PANGO14, 1, [Define if we have Pango 1.4 or newer.]),:) dnl ####################################################################### + dnl # Check if we should compile with X support + dnl ####################################################################### + if test "x$with_x" = "xyes" ; then + PKG_CHECK_MODULES(X11, x11, + [AC_DEFINE(HAVE_X, 1, [Define to 1 if you have X11])], + [AC_MSG_RESULT(no) + with_x=no]) + AC_SUBST(X11_LIBS) + AC_SUBST(X11_CFLAGS) + fi + + dnl ####################################################################### dnl # Check for XScreenSaver dnl ####################################################################### if test "x$enable_screensaver" = "xyes" ; then - old_LIBS="$LIBS" - LIBS="$LIBS $GTK_LIBS $x_libpath_add" - XSS_LIBS="" - XSS_HEADERS="" - AC_CHECK_LIB(Xext, XScreenSaverRegister,[XSS_LIBS="$X_LIBS $X_PRE_LIBS -lX11 -lXext $X_EXTRA_LIBS"],[],[-lX11 -lXext -lm]) - AC_CHECK_LIB(Xss, XScreenSaverRegister,[XSS_LIBS="$X_LIBS $X_PRE_LIBS -lX11 -lXext $X_LIBS $X_EXTRA_LIBS -lXss"],[],[-lX11 -lXext -lm]) - if test "x$XSS_LIBS" != "x"; then - oldCPPFLAGS="$CPPFLAGS" - CPPFLAGS="$CPPFLAGS $x_incpath_add" - AC_TRY_COMPILE([ + if test "x$with_x" = "xyes" ; then + old_LIBS="$LIBS" + LIBS="$LIBS $GTK_LIBS $x_libpath_add" + XSS_LIBS="" + XSS_HEADERS="" + AC_CHECK_LIB(Xext, XScreenSaverRegister,[XSS_LIBS="$X_LIBS $X_PRE_LIBS -lX11 -lXext $X_EXTRA_LIBS"],[],[-lX11 -lXext -lm]) + AC_CHECK_LIB(Xss, XScreenSaverRegister,[XSS_LIBS="$X_LIBS $X_PRE_LIBS -lX11 -lXext $X_LIBS $X_EXTRA_LIBS -lXss"],[],[-lX11 -lXext -lm]) + if test "x$XSS_LIBS" != "x"; then + oldCPPFLAGS="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $x_incpath_add" + AC_TRY_COMPILE([ #include <X11/Xlib.h> #include <X11/extensions/scrnsaver.h> - ], [], [], [enable_screensaver=no]) - CPPFLAGS="$oldCPPFLAGS" + ], [], [], [enable_screensaver=no]) + CPPFLAGS="$oldCPPFLAGS" + else + enable_screensaver=no + fi + LIBS="$old_LIBS" + + if test "x$enable_screensaver" = "xyes" ; then + AC_DEFINE(USE_SCREENSAVER, 1, [Define if we're using XScreenSaver.]) + AC_SUBST(XSS_LIBS) + fi else enable_screensaver=no fi - LIBS="$old_LIBS" - - if test "x$enable_screensaver" = "xyes" ; then - AC_DEFINE(USE_SCREENSAVER, 1, [Define if we're using XScreenSaver.]) - AC_SUBST(XSS_LIBS) - fi fi dnl ####################################################################### dnl # Check for X session management libs dnl ####################################################################### if test "x$enable_sm" = "xyes"; then - enable_sm=no - AC_CHECK_LIB(SM, SmcSaveYourselfDone, found_sm_lib=true, , [$x_libpath_add -lICE]) - if test "x$found_sm_lib" = "xtrue"; then - oldCPPFLAGS="$CPPFLAGS" - CPPFLAGS="$CPPFLAGS $x_incpath_add" - AC_CHECK_HEADERS(X11/SM/SMlib.h, SM_LIBS="$x_libpath_add -lSM -lICE" enable_sm=yes) - CPPFLAGS="$oldCPPFLAGS" + if test "x$with_x" = "xyes" ; then + enable_sm=no + AC_CHECK_LIB(SM, SmcSaveYourselfDone, found_sm_lib=true, , [$x_libpath_add -lICE]) + if test "x$found_sm_lib" = "xtrue"; then + oldCPPFLAGS="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $x_incpath_add" + AC_CHECK_HEADERS(X11/SM/SMlib.h, SM_LIBS="$x_libpath_add -lSM -lICE" enable_sm=yes) + CPPFLAGS="$oldCPPFLAGS" + fi + + if test "x$enable_sm" = "xyes"; then + AC_DEFINE(USE_SM, 1, [Define if we're using X Session Management.]) + AC_SUBST(SM_LIBS) + fi + else + enable_sm=no fi + fi - if test "x$enable_sm" = "xyes"; then - AC_DEFINE(USE_SM, 1, [Define if we're using X Session Management.]) - AC_SUBST(SM_LIBS) + dnl ####################################################################### + dnl # Check for X11 to allow the gestures plugin + dnl ####################################################################### + if test "x$enable_gestures" = "xyes"; then + if test "x$with_x" = "xno" ; then + enable_gestures=no fi fi @@ -450,10 +484,11 @@ dnl ####################################################################### if test "x$enable_cap" = "xyes"; then PKG_CHECK_MODULES(SQLITE3, sqlite3 >= 3.3,,[ - AC_MSG_RESULT(no) - enable_cap="no" - ]) + AC_MSG_RESULT(no) + enable_cap="no" + ]) fi + else # GTK enable_cap=no @@ -467,6 +502,8 @@ AM_CONDITIONAL(ENABLE_GTK, test "x$enable_gtkui" = "xyes") AM_CONDITIONAL(BUILD_GEVOLUTION, test "x$enable_gevolution" = "xyes") AM_CONDITIONAL(ENABLE_CAP, test "x$enable_cap" = "xyes") +AM_CONDITIONAL(ENABLE_GESTURES, test "x$enable_gestures" = "xyes") + dnl ####################################################################### dnl # Check for ncurses and other things used by the console UI @@ -531,11 +568,6 @@ fi fi fi - - PKG_CHECK_MODULES(X11, x11, - [AC_DEFINE(HAVE_X11, 1, [Define to 1 if you have X11])], [AC_MSG_RESULT(no)]) - AC_SUBST(X11_LIBS) - AC_SUBST(X11_CFLAGS) fi AC_SUBST(GNT_LIBS) @@ -858,7 +890,7 @@ fi if test "x$STATIC_PRPLS" = "xall" ; then - STATIC_PRPLS="bonjour gg irc jabber msn novell oscar qq sametime silc simple yahoo zephyr" + STATIC_PRPLS="bonjour gg irc jabber msn myspace novell oscar qq sametime silc simple yahoo zephyr" fi if test "x$have_meanwhile" != "xyes" ; then STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/sametime//'` @@ -904,6 +936,7 @@ irc) static_irc=yes ;; jabber) static_jabber=yes ;; msn) static_msn=yes ;; + myspace) static_myspace=yes ;; novell) static_novell=yes ;; oscar) static_oscar=yes ;; aim) static_oscar=yes ;; @@ -924,6 +957,7 @@ AM_CONDITIONAL(STATIC_IRC, test "x$static_irc" = "xyes") AM_CONDITIONAL(STATIC_JABBER, test "x$static_jabber" = "xyes") AM_CONDITIONAL(STATIC_MSN, test "x$static_msn" = "xyes") +AM_CONDITIONAL(STATIC_MYSPACE, test "x$static_myspace" = "xyes") AM_CONDITIONAL(STATIC_NOVELL, test "x$static_novell" = "xyes") AM_CONDITIONAL(STATIC_OSCAR, test "x$static_oscar" = "xyes") AM_CONDITIONAL(STATIC_QQ, test "x$static_qq" = "xyes") @@ -939,7 +973,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 novell oscar qq sametime silc simple yahoo zephyr" + DYNAMIC_PRPLS="bonjour gg irc jabber msn myspace novell oscar qq sametime silc simple yahoo zephyr" fi if test "x$have_meanwhile" != "xyes"; then DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/sametime//'` @@ -963,6 +997,7 @@ irc) dynamic_irc=yes ;; jabber) dynamic_jabber=yes ;; msn) dynamic_msn=yes ;; + myspace) dynamic_myspace=yes ;; novell) dynamic_novell=yes ;; oscar) dynamic_oscar=yes ;; aim) dynamic_oscar=yes ;; @@ -983,6 +1018,7 @@ AM_CONDITIONAL(DYNAMIC_IRC, test "x$dynamic_irc" = "xyes") AM_CONDITIONAL(DYNAMIC_JABBER, test "x$dynamic_jabber" = "xyes") AM_CONDITIONAL(DYNAMIC_MSN, test "x$dynamic_msn" = "xyes") +AM_CONDITIONAL(DYNAMIC_MYSPACE, test "x$dynamic_myspace" = "xyes") AM_CONDITIONAL(DYNAMIC_NOVELL, test "x$dynamic_novell" = "xyes") AM_CONDITIONAL(DYNAMIC_OSCAR, test "x$dynamic_oscar" = "xyes") AM_CONDITIONAL(DYNAMIC_QQ, test "x$dynamic_qq" = "xyes") @@ -1151,7 +1187,7 @@ AC_PATH_PROG([PYTHON], [python], [no]) fi - if test x"$python" = x"no" ; then + if test x"$PYTHON" = x"no" ; then AC_MSG_WARN([python interpreter not found in your path]) enable_dbus=no fi @@ -2137,6 +2173,7 @@ pidgin/pixmaps/status/Makefile pidgin/pixmaps/status/11/Makefile pidgin/pixmaps/status/11/scalable/Makefile + pidgin/pixmaps/status/11/rtl/Makefile pidgin/pixmaps/status/16/Makefile pidgin/pixmaps/status/16/rtl/Makefile pidgin/pixmaps/status/16/scalable/Makefile @@ -2187,6 +2224,7 @@ libpurple/protocols/irc/Makefile libpurple/protocols/jabber/Makefile libpurple/protocols/msn/Makefile + libpurple/protocols/myspace/Makefile libpurple/protocols/novell/Makefile libpurple/protocols/null/Makefile libpurple/protocols/oscar/Makefile @@ -2202,9 +2240,11 @@ libpurple/version.h share/Makefile share/sounds/Makefile + share/ca-certs/Makefile finch/Makefile finch/libgnt/Makefile finch/libgnt/gnt.pc + finch/libgnt/pygnt/Makefile finch/libgnt/wms/Makefile finch/plugins/Makefile po/Makefile.in @@ -2217,7 +2257,9 @@ echo echo Build GTK+ 2.x UI............. : $enable_gtkui echo Build console UI.............. : $enable_consoleui +echo Build for X11................. : $with_x echo +echo Enable Gestures............... : $enable_gestures echo Protocols to build dynamically : $DYNAMIC_PRPLS echo Protocols to link statically.. : $STATIC_PRPLS echo
--- a/doc/Makefile.am Sun Aug 19 13:40:34 2007 +0000 +++ b/doc/Makefile.am Wed Sep 12 19:11:38 2007 +0000 @@ -3,11 +3,13 @@ EXTRA_DIST = \ C-HOWTO.dox \ PERL-HOWTO.dox \ + SIGNAL-HOWTO.dox \ TCL-HOWTO.dox \ TracFooter.html \ TracHeader.html \ account-signals.dox \ blist-signals.dox \ + certificate-signals.dox \ cipher-signals.dox \ connection-signals.dox \ conversation-signals.dox \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/SIGNAL-HOWTO.dox Wed Sep 12 19:11:38 2007 +0000 @@ -0,0 +1,137 @@ +/** @page signal-howto Signals HOWTO + + @section Introduction + The libpurple signals interface is used for general event notification, such + as plugins being loaded or unloaded, allowing the GUI frontend to respond + appropriately to changing internal data. Unfortunately, its use is not at all + obvious from the information in the header files. This document uses code + snippets from the Pidgin/libpurple plugin systems to illustrate the proper + use of signals. + + @section overview Overview of Signals + Signals in libpurple are very similar to those in GTK+. When certain events + happen, a named signal is "emitted" from a certain object. Emitting the + signal triggers a series of callbacks that have been "connected" to that + signal for that object. These callbacks take appropriate action in response + to the signal. + + @section registering_signal Registering a Signal + The first step of using a signal is registering it with libpurple so that + callbacks may be connected to it. This is done using purple_signal_register() + Here is a slightly modified example from @c purple_plugins_init in + @c libpurple/plugin.c : + + @code + purple_signal_register( purple_plugins_get_handle(), /* Instance */ + "plugin-load", /* Signal name */ + purple_marshal_VOID__POINTER,/* Marshal function */ + NULL, /* Callback return value type */ + 1, /* Number of callback arguments (not including void *data) */ + purple_value_new(PURPLE_TYPE_SUBTYPE,PURPLE_SUBTYPE_PLUGIN) /* Type of first callback argument */ + ); + @endcode + + @subsection Instance + A reference to the object from which this signal is emitted, and to which + potential callbacks should be connected. In this case, it will be the entire + plugin module emitting the signal. + + @subsection signalname Signal Name + Unique identifier for the signal itself. + + @subsection therest Callback function definition + The rest of the arguments specify the form of the callback function. + + @subsubsection marshalfunc Marshal Function + @c purple_marshal_VOID__POINTER represents the callback function prototype, + not including a "data" argument, explained later. The form is + @c purple_marshal_RETURNVALUETYPE__ARG1TYPE_ARG2TYPE_ETC. See signals.h for + more possible types. + + In this case, the callback will have the form + @code + void cb(void *arg1, void *data) + @endcode + + If @c purple_marshal_BOOLEAN__POINTER_POINTER_POINTER were specified, it + would be: + @code + gboolean cb(void *arg1, void *arg2, void *arg3, void *data) + @endcode + + The @c void @c *data argument at the end of each callback function + provides the data argument given to purple_signal_connect() . + + @subsubsection cb_ret_type Callback return value type + In our case, this is NULL, meaning "returns void". + @todo This could be described better. + + @subsubsection num_args Number of arguments + The number of arguments (not including @c data ) that the callback function + will take. + + @subsubsection type_arg Type of argument + @c purple_value_new(PURPLE_TYPE_SUBTYPE,PURPLE_SUBTYPE_PLUGIN) specifies that + the first argument given to the callback will be a @c PurplePlugin* . You + will need as many "type of argument" arguments to purple_signal_register() as + you specified in "Number of arguments" above. + + @todo Describe this more. + + @see value.h + + @section connect Connecting to the signal + Once the signal is registered, you can connect callbacks to it. First, you + must define a callback function, such as this one from gtkplugin.c : + @code +static void plugin_load_cb(PurplePlugin *plugin, gpointer data) +{ + GtkTreeView *view = (GtkTreeView *)data; + plugin_loading_common(plugin, view, TRUE); +} + @endcode + Note that the callback function prototype matches that specified in the call + to purple_signal_register() above. + + Once the callback function is defined, you can connect it to the signal. + Again from gtkplugin.c , in @c pidgin_plugin_dialog_show() : + @code + purple_signal_connect(purple_plugins_get_handle(), "plugin-load", /* What to connect to */ + plugin_dialog, /* Object receiving the signal */ + PURPLE_CALLBACK(plugin_load_cb), /* Callback function */ + event_view, /* Data to pass to the callback function + ); + @endcode + + The first two arguments ("What to connect to") specify the object emitting + the signal (the plugin module) and what signal to listen for ("plugin-load"). + + The object receiving the signal is @c plugin_dialog , the Pidgin plugins + dialog. When @c plugin_dialog is deleted, then + @c purple_signals_disconnect_by_handle(plugin_dialog) should be called to + remove all signal connections it is associated with. + + The callback function is given using a helper macro, and finally the + @c data argument to be passed to @c plugin_load_cb is given as @c event_view, + a pointer to the GTK widget that @c plugin_load_cb needs to update. + + @section emit-signal Emitting a signal + Connecting callbacks to signals is all well and good, but how do you "fire" + the signal and trigger the callback? At some point, you must "emit" the + signal, which immediately calls all connected callbacks. + + As seen in @c purple_plugin_load() in plugin.c : + @code + purple_signal_emit(purple_plugins_get_handle(), "plugin-load", plugin); + @endcode + This causes the signal "plugin-load" to be emitted from the plugin module + (given by @c purple_plugins_get_handle() ), with the newly loaded plugin as + the argument to pass to any registered callback functions. + + In our example, @c plugin_load_cb is called immediately as + @code + plugin_load_cb(plugin, event_view); + @endcode + and does whatever it does. + + */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/certificate-signals.dox Wed Sep 12 19:11:38 2007 +0000 @@ -0,0 +1,31 @@ +/** @page certificate-signals Certificate Signals + + @signals + @signal certificate-stored + @signal certificate-deleted + @endsignals + + <hr> + + @signaldef certificate-stored + @signalproto +void (*certificate_stored)(PurpleCertificatePool *pool, const gchar *id, gpointer data); + @endsignalproto + @signaldesc + Emitted when a pool stores a certificate. Connect to the pool instance. + @param pool Pool the certificate has been stored into + @param id Key the certificate was stored under + @endsignaldef + + @signaldef certificate-deleted + @signalproto +void (*certificate_deleted)(PurpleCertificatePool *pool, const gchar *id, gpointer data); + @endsignalproto + @signaldesc + Emitted when a pool deletes a certificate. Connect to the pool instance. + @param pool Pool the certificate was deleted from + @param id Key that was deleted + @endsignaldef + + */ +// vim: syntax=c tw=75 et
--- a/doc/finch.1.in Sun Aug 19 13:40:34 2007 +0000 +++ b/doc/finch.1.in Wed Sep 12 19:11:38 2007 +0000 @@ -494,7 +494,7 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA .SH AUTHORS Sadrul Habib Chowdhury <\fIsadrul@users.sourceforge.net\fR>
--- a/doc/funniest_home_convos.txt Sun Aug 19 13:40:34 2007 +0000 +++ b/doc/funniest_home_convos.txt Wed Sep 12 19:11:38 2007 +0000 @@ -474,3 +474,16 @@ 19:23 <-- elb has quit (K-lined) +19:01 <user> whoa +19:01 <user> okay +19:01 <user> now when i try to go into the left over files after the + uninstall +19:01 <user> something is seriously wrong because it says "the files on the + c drive are not formatted, would you like to format?" +19:03 <user> manually deleting the folder from command gives me a "Data + error (cyclic redundancy check)." +19:03 <elb> remember, one line per thought +19:03 <elb> and yes, you have something wrong with your computer, we've + established that +19:03 <user> its functioning just fine +
--- a/doc/gtkconv-signals.dox Sun Aug 19 13:40:34 2007 +0000 +++ b/doc/gtkconv-signals.dox Wed Sep 12 19:11:38 2007 +0000 @@ -8,6 +8,8 @@ @signal displaying-chat-msg @signal displayed-chat-msg @signal conversation-switched + @signal conversation-hiding + @signal conversation-displayed @endsignals <hr> @@ -116,5 +118,23 @@ @param new_conv The now active conversation. @endsignaldef + @signaldef conversation-hiding + @signalproto +void (*conversation_hiding)(PidginConversation *gtkconv); + @endsignalproto + @signaldesc + Emitted immediately before an existing conversation is hidden. + @param gtkconv The PidginConversation + @endsignaldef + + @signaldef conversation-displayed + @signalproto +void (*conversation_displayed)(PidginConversation *gtkconv); + @endsignalproto + @signaldesc + Emitted right after the Pidgin UI is reattached to a conversation. + @param gtkconv The PidginConversation + @endsignaldef + */ // vim: syntax=c tw=75 et
--- a/doc/pidgin.1.in Sun Aug 19 13:40:34 2007 +0000 +++ b/doc/pidgin.1.in Wed Sep 12 19:11:38 2007 +0000 @@ -526,7 +526,7 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA .SH AUTHORS Pidgin's active developers are:
--- a/finch/Makefile.am Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/Makefile.am Wed Sep 12 19:11:38 2007 +0000 @@ -14,6 +14,7 @@ finch_SOURCES = \ gntaccount.c \ gntblist.c \ + gntcertmgr.c \ gntconn.c \ gntconv.c \ gntdebug.c \ @@ -32,6 +33,7 @@ finch_headers = \ gntaccount.h \ gntblist.h \ + gntcertmgr.h \ gntconn.h \ gntconv.h \ gntdebug.h \
--- a/finch/finch.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/finch.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "finch.h" @@ -360,9 +360,7 @@ purple_set_blist(purple_blist_new()); purple_blist_load(); - /* TODO: Move prefs loading into purple_prefs_init() */ - purple_prefs_load(); - purple_prefs_update_old(); + /* TODO: should this be moved into finch_prefs_init() ? */ finch_prefs_update_old(); /* load plugins we had when we quit */ @@ -412,6 +410,8 @@ { signal(SIGPIPE, SIG_IGN); + g_thread_init(NULL); + g_set_prgname("Finch"); #if GLIB_CHECK_VERSION(2,2,0) g_set_application_name(_("Finch"));
--- a/finch/finch.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/finch.h Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include <glib.h>
--- a/finch/gntaccount.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/gntaccount.c Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include <gnt.h> #include <gntbox.h> @@ -169,7 +169,7 @@ purple_account_set_remember_password(account, gnt_check_box_get_checked(GNT_CHECK_BOX(dialog->remember))); value = gnt_entry_get_text(GNT_ENTRY(dialog->password)); - if (value && *value && purple_account_get_remember_password(account)) + if (value && *value) purple_account_set_password(account, value); else purple_account_set_password(account, NULL); @@ -222,6 +222,11 @@ /* XXX: Proxy options */ + if (accounts.window && accounts.tree) { + gnt_tree_set_selected(GNT_TREE(accounts.tree), account); + gnt_box_give_focus_to_child(GNT_BOX(accounts.window), accounts.tree); + } + gnt_widget_destroy(dialog->window); } @@ -710,6 +715,11 @@ gnt_widget_show(accounts.window); } +void finch_account_dialog_show(PurpleAccount *account) +{ + edit_account(account); +} + static gpointer finch_accounts_get_handle() {
--- a/finch/gntaccount.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/gntaccount.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _GNT_ACCOUNT_H #define _GNT_ACCOUNT_H @@ -54,6 +54,13 @@ */ void finch_accounts_show_all(void); +/** + * Show the edit dialog for an account. + * + * @param account The account to edit, or @c NULL to create a new account. + */ +void finch_account_dialog_show(PurpleAccount *account); + /*@}*/ #endif
--- a/finch/gntblist.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/gntblist.c Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "finch.h" @@ -2139,20 +2139,29 @@ } } -static void -account_signed_on_cb(PurpleConnection *pc, gpointer null) +static gboolean +auto_join_chats(gpointer data) { PurpleBlistNode *node; + PurpleConnection *pc = data; + PurpleAccount *account = purple_connection_get_account(pc); for (node = purple_blist_get_root(); node; node = purple_blist_node_next(node, FALSE)) { if (PURPLE_BLIST_NODE_IS_CHAT(node)) { PurpleChat *chat = (PurpleChat*)node; - if (chat->account == purple_connection_get_account(pc) && + if (chat->account == account && purple_blist_node_get_bool(node, "gnt-autojoin")) serv_join_chat(purple_account_get_connection(chat->account), chat->components); } } + return FALSE; +} + +static void +account_signed_on_cb(PurpleConnection *gc, gpointer null) +{ + g_idle_add(auto_join_chats, gc); } static void toggle_pref_cb(GntMenuItem *item, gpointer n)
--- a/finch/gntblist.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/gntblist.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _GNT_BLIST_H #define _GNT_BLIST_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/finch/gntcertmgr.c Wed Sep 12 19:11:38 2007 +0000 @@ -0,0 +1,340 @@ +/** + * @file gntcertmgr.c GNT Certificate Manager API + * @ingroup finch + * + * finch + * + * Finch is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * 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 "internal.h" + +#include "certificate.h" +#include "debug.h" +#include "notify.h" +#include "request.h" + +#include "finch.h" +#include "gntcertmgr.h" + +#include "gntbutton.h" +#include "gntlabel.h" +#include "gnttree.h" +#include "gntutils.h" +#include "gntwindow.h" + +struct { + GntWidget *window; + GntWidget *tree; + PurpleCertificatePool *pool; +} certmgr; + +/* Pretty much Xerox of gtkcertmgr */ + +/* Add certificate */ +static void +tls_peers_mgmt_import_ok2_cb(gpointer data, const char *result) +{ + PurpleCertificate *crt = (PurpleCertificate *) data; + const char *id = result; + + /* TODO: Perhaps prompt if you're overwriting a cert? */ + + purple_certificate_pool_store(purple_certificate_find_pool("x509", "tls_peers"), id, crt); + purple_certificate_destroy(crt); +} + +static void +tls_peers_mgmt_import_cancel2_cb(gpointer data, const char *result) +{ + PurpleCertificate *crt = (PurpleCertificate *) data; + purple_certificate_destroy(crt); +} + +static void +tls_peers_mgmt_import_ok_cb(gpointer data, const char *filename) +{ + PurpleCertificateScheme *x509; + PurpleCertificate *crt; + + x509 = purple_certificate_pool_get_scheme(purple_certificate_find_pool("x509", "tls_peers")); + + crt = purple_certificate_import(x509, filename); + + if (crt != NULL) { + gchar *default_hostname; + default_hostname = purple_certificate_get_subject_name(crt); + purple_request_input(NULL, + _("Certificate Import"), + _("Specify a hostname"), + _("Type the host name this certificate is for."), + default_hostname, FALSE, FALSE, NULL, + _("OK"), G_CALLBACK(tls_peers_mgmt_import_ok2_cb), + _("Cancel"), G_CALLBACK(tls_peers_mgmt_import_cancel2_cb), + NULL, NULL, NULL, + crt); + g_free(default_hostname); + } else { + gchar * secondary; + secondary = g_strdup_printf(_("File %s could not be imported.\nMake sure that the file is readable and in PEM format.\n"), filename); + purple_notify_error(NULL, + _("Certificate Import Error"), + _("X.509 certificate import failed"), + secondary); + g_free(secondary); + } +} + +static void +add_cert_cb(GntWidget *button, gpointer null) +{ + purple_request_file(NULL, + _("Select a PEM certificate"), + "certificate.pem", + FALSE, + G_CALLBACK(tls_peers_mgmt_import_ok_cb), + NULL, + NULL, NULL, NULL, NULL ); +} + +/* Save certs in some file */ +static void +tls_peers_mgmt_export_ok_cb(gpointer data, const char *filename) +{ + PurpleCertificate *crt = (PurpleCertificate *) data; + + if (!purple_certificate_export(filename, crt)) { + gchar * secondary; + + secondary = g_strdup_printf(_("Export to file %s failed.\nCheck that you have write permission to the target path\n"), filename); + purple_notify_error(NULL, + _("Certificate Export Error"), + _("X.509 certificate export failed"), + secondary); + g_free(secondary); + } + + purple_certificate_destroy(crt); +} + +static void +save_cert_cb(GntWidget *button, gpointer null) +{ + PurpleCertificate *crt; + const char *key; + + if (!certmgr.window) + return; + + key = gnt_tree_get_selection_data(GNT_TREE(certmgr.tree)); + if (!key) + return; + + crt = purple_certificate_pool_retrieve(certmgr.pool, key); + if (!crt) { + purple_debug_error("gntcertmgr/tls_peers_mgmt", + "Id %s was not in the peers cache?!\n", key); + return; + } + + purple_request_file((void*)key, + _("PEM X.509 Certificate Export"), + "certificate.pem", TRUE, + G_CALLBACK(tls_peers_mgmt_export_ok_cb), + G_CALLBACK(purple_certificate_destroy), + NULL, NULL, NULL, + crt); +} + +/* Show information about a cert */ +static void +info_cert_cb(GntWidget *button, gpointer null) +{ + const char *key; + PurpleCertificate *crt; + gchar *subject; + GByteArray *fpr_sha1; + gchar *fpr_sha1_asc; + gchar *primary, *secondary; + + if (!certmgr.window) + return; + + key = gnt_tree_get_selection_data(GNT_TREE(certmgr.tree)); + if (!key) + return; + + crt = purple_certificate_pool_retrieve(certmgr.pool, key); + g_return_if_fail(crt); + + primary = g_strdup_printf(_("Certificate for %s"), key); + + fpr_sha1 = purple_certificate_get_fingerprint_sha1(crt); + fpr_sha1_asc = purple_base16_encode_chunked(fpr_sha1->data, + fpr_sha1->len); + subject = purple_certificate_get_subject_name(crt); + + secondary = g_strdup_printf(_("Common name: %s\n\nSHA1 fingerprint:\n%s"), subject, fpr_sha1_asc); + + purple_notify_info(NULL, + _("SSL Host Certificate"), primary, secondary); + + g_free(primary); + g_free(secondary); + g_byte_array_free(fpr_sha1, TRUE); + g_free(fpr_sha1_asc); + g_free(subject); + purple_certificate_destroy(crt); +} + +/* Delete a cert */ +static void +tls_peers_mgmt_delete_confirm_cb(gchar *id, gint dontcare) +{ + if (!purple_certificate_pool_delete(certmgr.pool, id)) { + purple_debug_warning("gntcertmgr/tls_peers_mgmt", + "Deletion failed on id %s\n", id); + }; + + g_free(id); +} + +static void +delete_cert_cb(GntWidget *button, gpointer null) +{ + gchar *primary; + const char *key; + + if (!certmgr.window) + return; + + key = gnt_tree_get_selection_data(GNT_TREE(certmgr.tree)); + if (!key) + return; + + primary = g_strdup_printf(_("Really delete certificate for %s?"), key); + + purple_request_close_with_handle((void *)key); + purple_request_yes_no((void *)key, _("Confirm certificate delete"), + primary, NULL, + 2, + NULL, NULL, NULL, + g_strdup(key), + tls_peers_mgmt_delete_confirm_cb, + g_free); + + g_free(primary); +} + +/* populate the list */ +static void +populate_cert_list() +{ + GList *idlist, *l; + + if (!certmgr.window) + return; + + gnt_tree_remove_all(GNT_TREE(certmgr.tree)); + + idlist = purple_certificate_pool_get_idlist(purple_certificate_find_pool("x509", "tls_peers")); + for (l = idlist; l; l = l->next) { + gnt_tree_add_row_last(GNT_TREE(certmgr.tree), g_strdup(l->data), + gnt_tree_create_row(GNT_TREE(certmgr.tree), l->data), NULL); + } + purple_certificate_pool_destroy_idlist(idlist); +} + +static void +cert_list_added(PurpleCertificatePool *pool, const char *id, gpointer null) +{ + g_return_if_fail(certmgr.window); + gnt_tree_add_row_last(GNT_TREE(certmgr.tree), g_strdup(id), + gnt_tree_create_row(GNT_TREE(certmgr.tree), id), NULL); +} + +static void +cert_list_removed(PurpleCertificatePool *pool, const char *id, gpointer null) +{ + g_return_if_fail(certmgr.window); + purple_request_close_with_handle((void*)id); + gnt_tree_remove(GNT_TREE(certmgr.tree), (void*)id); +} + +void finch_certmgr_show(void) +{ + GntWidget *win, *tree, *box, *button; + PurpleCertificatePool *pool; + + if (certmgr.window) { + gnt_window_present(certmgr.window); + return; + } + + certmgr.window = win = gnt_vwindow_new(FALSE); + gnt_box_set_title(GNT_BOX(win), _("Certificate Manager")); + gnt_box_set_pad(GNT_BOX(win), 0); + + certmgr.tree = tree = gnt_tree_new(); + gnt_tree_set_hash_fns(GNT_TREE(tree), g_str_hash, g_str_equal, g_free); + gnt_tree_set_column_title(GNT_TREE(tree), 0, _("Hostname")); + gnt_tree_set_show_title(GNT_TREE(tree), TRUE); + + gnt_box_add_widget(GNT_BOX(win), tree); + + box = gnt_hbox_new(FALSE); + gnt_box_add_widget(GNT_BOX(win), box); + + button = gnt_button_new(_("Add")); + gnt_box_add_widget(GNT_BOX(box), button); + g_signal_connect(G_OBJECT(button), "activate", G_CALLBACK(add_cert_cb), NULL); + gnt_util_set_trigger_widget(GNT_WIDGET(tree), GNT_KEY_INS, button); + + button = gnt_button_new(_("Save")); + gnt_box_add_widget(GNT_BOX(box), button); + g_signal_connect(G_OBJECT(button), "activate", G_CALLBACK(save_cert_cb), NULL); + + button = gnt_button_new(_("Info")); + gnt_box_add_widget(GNT_BOX(box), button); + g_signal_connect(G_OBJECT(button), "activate", G_CALLBACK(info_cert_cb), NULL); + + button = gnt_button_new(_("Delete")); + gnt_box_add_widget(GNT_BOX(box), button); + g_signal_connect(G_OBJECT(button), "activate", G_CALLBACK(delete_cert_cb), NULL); + gnt_util_set_trigger_widget(GNT_WIDGET(tree), GNT_KEY_DEL, button); + + button = gnt_button_new(_("Close")); + gnt_box_add_widget(GNT_BOX(box), button); + g_signal_connect_swapped(G_OBJECT(button), "activate", G_CALLBACK(gnt_widget_destroy), win); + + g_signal_connect_swapped(G_OBJECT(win), "destroy", G_CALLBACK(g_nullify_pointer), &certmgr.window); + + populate_cert_list(); + + pool = certmgr.pool = purple_certificate_find_pool("x509", "tls_peers"); + purple_signal_connect(pool, "certificate-stored", + win, PURPLE_CALLBACK(cert_list_added), NULL); + purple_signal_connect(pool, "certificate-deleted", + win, PURPLE_CALLBACK(cert_list_removed), NULL); + g_signal_connect(G_OBJECT(win), "destroy", G_CALLBACK(purple_signals_disconnect_by_handle), NULL); + + gnt_widget_show(certmgr.window); +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/finch/gntcertmgr.h Wed Sep 12 19:11:38 2007 +0000 @@ -0,0 +1,31 @@ +/** + * @file gntcertmgr.h GNT Certificate Manager API + * @ingroup finch + * + * finch + * + * Finch is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * 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 _GNT_CERTMGR_H +#define _GNT_CERTMGR_H + +void finch_certmgr_show(void); + +#endif
--- a/finch/gntconn.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/gntconn.c Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "finch.h" @@ -30,6 +30,7 @@ #include "debug.h" #include "request.h" +#include "gntaccount.h" #include "gntconn.h" #define INITIAL_RECON_DELAY_MIN 8000 @@ -87,6 +88,18 @@ } static void +ce_modify_account_cb(PurpleAccount *account) +{ + finch_account_dialog_show(account); +} + +static void +ce_enable_account_cb(PurpleAccount *account) +{ + purple_account_set_enabled(account, FINCH_UI, TRUE); +} + +static void finch_connection_report_disconnect(PurpleConnection *gc, const char *text) { FinchAutoRecon *info; @@ -114,7 +127,13 @@ secondary = g_strdup_printf(_("%s\n\n" "Finch will not attempt to reconnect the account until you " "correct the error and re-enable the account."), text); - purple_notify_error(NULL, NULL, primary, secondary); + + purple_request_action(account, NULL, primary, secondary, 2, + account, NULL, NULL, + account, 3, + _("OK"), NULL, + _("Modify Account"), PURPLE_CALLBACK(ce_modify_account_cb), + _("Re-enable Account"), PURPLE_CALLBACK(ce_enable_account_cb)); g_free(act); g_free(primary);
--- a/finch/gntconn.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/gntconn.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _GNT_CONN_H #define _GNT_CONN_H
--- a/finch/gntconv.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/gntconv.c Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include <string.h> @@ -37,6 +37,7 @@ #include "gntdebug.h" #include "gntplugin.h" #include "gntprefs.h" +#include "gntsound.h" #include "gntstatus.h" #include "gntpounce.h" @@ -142,6 +143,11 @@ } g_free(error); } + else if (!purple_account_is_connected(ggconv->active_conv->account)) + { + purple_conversation_write(ggconv->active_conv, "", _("Message was not sent, because you are not signed on."), + PURPLE_MESSAGE_ERROR | PURPLE_MESSAGE_NO_LOG, time(NULL)); + } else { char *escape = g_markup_escape_text(text, -1); @@ -342,6 +348,53 @@ } static void +toggle_logging_cb(GntMenuItem *item, gpointer ggconv) +{ + FinchConv *fc = ggconv; + PurpleConversation *conv = fc->active_conv; + gboolean logging = gnt_menuitem_check_get_checked(GNT_MENU_ITEM_CHECK(item)); + GList *iter; + + if (logging == purple_conversation_is_logging(conv)) + return; + + /* Xerox */ + if (logging) { + /* Enable logging first so the message below can be logged. */ + purple_conversation_set_logging(conv, TRUE); + + purple_conversation_write(conv, NULL, + _("Logging started. Future messages in this conversation will be logged."), + conv->logs ? (PURPLE_MESSAGE_SYSTEM) : + (PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LOG), + time(NULL)); + } else { + purple_conversation_write(conv, NULL, + _("Logging stopped. Future messages in this conversation will not be logged."), + conv->logs ? (PURPLE_MESSAGE_SYSTEM) : + (PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LOG), + time(NULL)); + + /* Disable the logging second, so that the above message can be logged. */ + purple_conversation_set_logging(conv, FALSE); + } + + /* Each conversation with the same person will have the same logging setting */ + for (iter = fc->list; iter; iter = iter->next) { + if (iter->data == conv) + continue; + purple_conversation_set_logging(iter->data, logging); + } +} + +static void +toggle_sound_cb(GntMenuItem *item, gpointer ggconv) +{ + FinchConv *fc = ggconv; + fc->flags ^= FINCH_CONV_NO_SOUND; +} + +static void send_to_cb(GntMenuItem *m, gpointer n) { PurpleAccount *account = g_object_get_data(G_OBJECT(m), "purple_account"); @@ -447,6 +500,18 @@ generate_send_to_menu(ggc); } + + item = gnt_menuitem_check_new(_("Enable Logging")); + gnt_menuitem_check_set_checked(GNT_MENU_ITEM_CHECK(item), + purple_conversation_is_logging(ggc->active_conv)); + gnt_menu_add_item(GNT_MENU(sub), item); + gnt_menuitem_set_callback(item, toggle_logging_cb, ggc); + + item = gnt_menuitem_check_new(_("Enable Sounds")); + gnt_menuitem_check_set_checked(GNT_MENU_ITEM_CHECK(item), + !(ggc->flags & FINCH_CONV_NO_SOUND)); + gnt_menu_add_item(GNT_MENU(sub), item); + gnt_menuitem_set_callback(item, toggle_sound_cb, ggc); } static void @@ -492,6 +557,12 @@ else ggc = g_new0(FinchConv, 1); + /* Each conversation with the same person will have the same logging setting */ + if (ggc->list) { + purple_conversation_set_logging(conv, + purple_conversation_is_logging(ggc->list->data)); + } + ggc->list = g_list_prepend(ggc->list, conv); ggc->active_conv = conv; conv->ui_data = ggc; @@ -542,6 +613,7 @@ gnt_tree_set_col_width(GNT_TREE(tree), 0, 1); /* The flag column */ gnt_tree_set_compare_func(GNT_TREE(tree), (GCompareFunc)g_utf8_collate); gnt_tree_set_hash_fns(GNT_TREE(tree), g_str_hash, g_str_equal, g_free); + gnt_tree_set_search_column(GNT_TREE(tree), 1); GNT_WIDGET_SET_FLAGS(tree, GNT_WIDGET_NO_BORDER); gnt_box_add_widget(GNT_BOX(hbox), ggc->tv); gnt_box_add_widget(GNT_BOX(hbox), tree); @@ -581,6 +653,9 @@ g_signal_connect(G_OBJECT(ggc->entry), "text_changed", G_CALLBACK(send_typing_notification), ggc); } + if (!finch_sound_is_enabled()) + ggc->flags |= FINCH_CONV_NO_SOUND; + gg_create_menu(ggc); g_free(title); @@ -825,6 +900,23 @@ gnt_tree_change_text(GNT_TREE(ggc->u.chat->userlist), (gpointer)user, 0, chat_flag_text(cb->flags)); } +static void +finch_conv_present(PurpleConversation *conv) +{ + FinchConv *fc = FINCH_CONV(conv); + if (fc && fc->window) + return gnt_window_present(fc->window); +} + +static gboolean +finch_conv_has_focus(PurpleConversation *conv) +{ + FinchConv *fc = FINCH_CONV(conv); + if (fc && fc->window) + return gnt_widget_has_focus(fc->window); + return FALSE; +} + static PurpleConversationUiOps conv_ui_ops = { finch_create_conversation, @@ -836,8 +928,8 @@ finch_chat_rename_user, finch_chat_remove_users, finch_chat_update_user, - NULL, /* present */ - NULL, /* has_focus */ + finch_conv_present, /* present */ + finch_conv_has_focus, /* has_focus */ NULL, /* custom_smiley_add */ NULL, /* custom_smiley_write */ NULL, /* custom_smiley_close */ @@ -915,6 +1007,7 @@ { FinchConv *ggconv = conv->ui_data; gnt_text_view_clear(GNT_TEXT_VIEW(ggconv->tv)); + purple_conversation_clear_message_history(conv); return PURPLE_CMD_STATUS_OK; }
--- a/finch/gntconv.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/gntconv.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _GNT_CONV_H #define _GNT_CONV_H @@ -43,6 +43,11 @@ typedef struct _FinchConvChat FinchConvChat; typedef struct _FinchConvIm FinchConvIm; +typedef enum +{ + FINCH_CONV_NO_SOUND = 1 << 0, +} FinchConversationFlag; + struct _FinchConv { GList *list; @@ -53,7 +58,7 @@ GntWidget *tv; /* text-view */ GntWidget *menu; GntWidget *info; - void *pad; + FinchConversationFlag flags; union {
--- a/finch/gntdebug.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/gntdebug.c Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include <gnt.h> #include <gntbox.h>
--- a/finch/gntdebug.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/gntdebug.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _GNT_DEBUG_H #define _GNT_DEBUG_H
--- a/finch/gntft.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/gntft.c Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include <gnt.h> #include <gntbox.h> @@ -193,6 +193,8 @@ g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(finch_xfer_dialog_destroy), NULL); gnt_box_set_toplevel(GNT_BOX(window), TRUE); gnt_box_set_title(GNT_BOX(window), _("File Transfers")); + gnt_box_set_fill(GNT_BOX(window), TRUE); + gnt_box_set_alignment(GNT_BOX(window), GNT_ALIGN_MID); xfer_dialog->tree = tree = gnt_tree_new_with_columns(NUM_COLUMNS); gnt_tree_set_column_titles(GNT_TREE(tree), _("Progress"), _("Filename"), _("Size"), _("Speed"), _("Remaining"), _("Status")); @@ -219,7 +221,7 @@ G_CALLBACK(toggle_clear_finished_cb), NULL); gnt_box_add_widget(GNT_BOX(window), checkbox); - bbox = gnt_hbox_new(TRUE); + bbox = gnt_hbox_new(FALSE); xfer_dialog->remove_button = button = gnt_button_new(_("Remove")); g_signal_connect(G_OBJECT(button), "activate", @@ -425,8 +427,11 @@ g_free(remaining_str); g_free(kbsec); if (purple_xfer_is_completed(xfer)) { + char *msg = g_strdup_printf(_("The file was saved as %s."), purple_xfer_get_local_filename(xfer)); gnt_tree_change_text(GNT_TREE(xfer_dialog->tree), xfer, COLUMN_STATUS, _("Finished")); gnt_tree_change_text(GNT_TREE(xfer_dialog->tree), xfer, COLUMN_REMAINING, _("Finished")); + purple_xfer_conversation_write(xfer, msg, FALSE); + g_free(msg); } else { gnt_tree_change_text(GNT_TREE(xfer_dialog->tree), xfer, COLUMN_STATUS, _("Transferring")); }
--- a/finch/gntft.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/gntft.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _FINCHFT_H_ #define _FINCHFT_H_
--- a/finch/gntidle.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/gntidle.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */
--- a/finch/gntidle.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/gntidle.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _GNT_IDLE_H_ #define _GNT_IDLE_H_
--- a/finch/gntnotify.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/gntnotify.c Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include <gnt.h> #include <gntbox.h>
--- a/finch/gntnotify.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/gntnotify.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _GNT_NOTIFY_H #define _GNT_NOTIFY_H
--- a/finch/gntplugin.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/gntplugin.c Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include <gnt.h> #include <gntbox.h>
--- a/finch/gntplugin.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/gntplugin.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _GNT_PLUGIN_H #define _GNT_PLUGIN_H
--- a/finch/gntpounce.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/gntpounce.c Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */ #include <gnt.h> @@ -452,7 +452,7 @@ gnt_box_add_widget(GNT_BOX(window), gnt_line_new(FALSE)); /* Now the button box! */ - bbox = gnt_hbox_new(TRUE); + bbox = gnt_hbox_new(FALSE); /* Cancel button */ button = gnt_button_new(_("Cancel")); @@ -613,6 +613,12 @@ static void pounces_manager_add_cb(GntButton *button, gpointer user_data) { + if (purple_accounts_get_all() == NULL) { + purple_notify_error(NULL, _("Cannot create pounce"), + _("You do not have any accounts."), + _("You must create an account first before you can create a pounce.")); + return; + } finch_pounce_editor_show(NULL, NULL, NULL); } @@ -622,7 +628,8 @@ { PouncesManager *dialog = user_data; PurplePounce *pounce = gnt_tree_get_selection_data(GNT_TREE(dialog->tree)); - finch_pounce_editor_show(NULL, NULL, pounce); + if (pounce) + finch_pounce_editor_show(NULL, NULL, pounce); } static void @@ -645,6 +652,9 @@ char *buf; pounce = (PurplePounce *)gnt_tree_get_selection_data(GNT_TREE(dialog->tree)); + if (pounce == NULL) + return; + account = purple_pounce_get_pouncer(pounce); pouncer = purple_account_get_username(account); pouncee = purple_pounce_get_pouncee(pounce); @@ -696,7 +706,7 @@ gnt_box_add_widget(GNT_BOX(win), tree); /* Button box. */ - bbox = gnt_hbox_new(TRUE); + bbox = gnt_hbox_new(FALSE); /* Add button */ button = gnt_button_new(_("Add"));
--- a/finch/gntpounce.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/gntpounce.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _FINCHPOUNCE_H_ #define _FINCHPOUNCE_H_
--- a/finch/gntprefs.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/gntprefs.c Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "finch.h"
--- a/finch/gntprefs.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/gntprefs.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _GNT_PREFS_H #define _GNT_PREFS_H
--- a/finch/gntrequest.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/gntrequest.c Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include <gnt.h> #include <gntbox.h> @@ -198,7 +198,7 @@ static void * finch_request_choice(const char *title, const char *primary, - const char *secondary, unsigned int default_value, + const char *secondary, int default_value, const char *ok_text, GCallback ok_cb, const char *cancel_text, GCallback cancel_cb, PurpleAccount *account, const char *who, PurpleConversation *conv, @@ -244,7 +244,7 @@ static void* finch_request_action(const char *title, const char *primary, - const char *secondary, unsigned int default_value, + const char *secondary, int default_value, PurpleAccount *account, const char *who, PurpleConversation *conv, const char *ui_hint, void *user_data, size_t actioncount, va_list actions) @@ -368,6 +368,22 @@ purple_request_close(PURPLE_REQUEST_FIELDS, button); } +static void +update_selected_account(GntEntry *screenname, const char *start, const char *end, + GntComboBox *accountlist) +{ + GList *accounts = gnt_tree_get_rows(GNT_TREE(accountlist->dropdown)); + const char *name = gnt_entry_get_text(screenname); + while (accounts) { + if (purple_find_buddy(accounts->data, name)) { + gnt_combo_box_set_selected(accountlist, accounts->data); + gnt_widget_draw(GNT_WIDGET(accountlist)); + break; + } + accounts = accounts->next; + } +} + static void * finch_request_fields(const char *title, const char *primary, const char *secondary, PurpleRequestFields *allfields, @@ -378,6 +394,7 @@ { GntWidget *window, *box; GList *grlist; + GntWidget *screenname = NULL, *accountlist = NULL; window = setup_request_window(title, primary, secondary, PURPLE_REQUEST_FIELDS); @@ -439,6 +456,7 @@ gnt_entry_add_suggest(GNT_ENTRY(entry), purple_buddy_get_name((PurpleBuddy*)node)); } gnt_entry_set_always_suggest(GNT_ENTRY(entry), TRUE); + screenname = entry; } else if (hint && !strcmp(hint, "group")) { PurpleBlistNode *node; for (node = purple_blist_get_root(); node; node = node->next) { @@ -556,6 +574,7 @@ gnt_combo_box_set_selected(GNT_COMBO_BOX(combo), account); } gnt_widget_set_size(combo, 20, 3); /* ew */ + accountlist = combo; } else { @@ -576,6 +595,10 @@ setup_default_callback(window, cancel_cb, userdata); gnt_widget_show(window); + if (screenname && accountlist) { + g_signal_connect(screenname, "completion", G_CALLBACK(update_selected_account), accountlist); + } + g_object_set_data(G_OBJECT(window), "fields", allfields); return window;
--- a/finch/gntrequest.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/gntrequest.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _GNT_REQUEST_H #define _GNT_REQUEST_H
--- a/finch/gntsound.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/gntsound.c Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "internal.h" #include "finch.h" @@ -40,6 +40,8 @@ #include "sound.h" #include "util.h" +#include "gntconv.h" + #include "gntbox.h" #include "gntwindow.h" #include "gntcombobox.h" @@ -173,7 +175,8 @@ has_focus = purple_conversation_has_focus(conv); - if (has_focus && !purple_prefs_get_bool(make_pref("/conv_focus"))) + if ((gntconv->flags & FINCH_CONV_NO_SOUND) || + (has_focus && !purple_prefs_get_bool(make_pref("/conv_focus")))) { return; } @@ -409,14 +412,14 @@ GError *err = NULL; switch (GST_MESSAGE_TYPE (msg)) { - case GST_MESSAGE_EOS: - gst_element_set_state(play, GST_STATE_NULL); - gst_object_unref(GST_OBJECT(play)); - break; case GST_MESSAGE_ERROR: gst_message_parse_error(msg, &err, NULL); purple_debug_error("gstreamer", "%s\n", err->message); g_error_free(err); + /* fall-through and clean up */ + case GST_MESSAGE_EOS: + gst_element_set_state(play, GST_STATE_NULL); + gst_object_unref(GST_OBJECT(play)); break; case GST_MESSAGE_WARNING: gst_message_parse_warning(msg, &err, NULL); @@ -670,28 +673,34 @@ { PurpleSoundEventID id = GPOINTER_TO_INT(gnt_tree_get_selection_data(GNT_TREE(pref_dialog->events))); FinchSoundEvent * event = &sounds[id]; - char *enabled, *file, *tmpfile; + char *enabled, *file, *tmpfile, *volpref; gboolean temp_value; + int volume; enabled = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s/enabled/%s", finch_sound_get_active_profile(), event->pref); file = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s/file/%s", finch_sound_get_active_profile(), event->pref); + volpref = g_strdup(make_pref("/volume")); temp_value = purple_prefs_get_bool(enabled); - tmpfile = g_strdup(purple_prefs_get_string(file)); + tmpfile = g_strdup(purple_prefs_get_path(file)); + volume = purple_prefs_get_int(volpref); - purple_prefs_set_string(file, event->file); + purple_prefs_set_path(file, event->file); if (!temp_value) purple_prefs_set_bool(enabled, TRUE); + purple_prefs_set_int(volpref, gnt_slider_get_value(GNT_SLIDER(pref_dialog->volume))); purple_sound_play_event(id, NULL); if (!temp_value) purple_prefs_set_bool(enabled, FALSE); - purple_prefs_set_string(file, tmpfile); + purple_prefs_set_path(file, tmpfile); + purple_prefs_set_int(volpref, volume); g_free(enabled); g_free(file); g_free(tmpfile); + g_free(volpref); } static void @@ -990,6 +999,8 @@ pref_dialog->volume = slider = gnt_slider_new(FALSE, 100, 0); gnt_slider_set_step(GNT_SLIDER(slider), 5); + gnt_slider_set_small_step(GNT_SLIDER(slider), 1); + gnt_slider_set_large_step(GNT_SLIDER(slider), 20); label = gnt_label_new(""); gnt_slider_reflect_label(GNT_SLIDER(slider), GNT_LABEL(label)); gnt_box_set_pad(GNT_BOX(tmpbox), 1); @@ -1053,7 +1064,22 @@ load_pref_window(finch_sound_get_active_profile()); gnt_widget_show(win); -} +} + +gboolean finch_sound_is_enabled(void) +{ + const char *pref = make_pref("/method"); + const char *method = purple_prefs_get_string(pref); + + if (!method) + return FALSE; + if (strcmp(method, "nosound") == 0) + return FALSE; + if (purple_prefs_get_int(make_pref("/volume")) <= 0) + return FALSE; + + return TRUE; +} static PurpleSoundUiOps sound_ui_ops = {
--- a/finch/gntsound.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/gntsound.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _GNT_SOUND_H #define _GNT_SOUND_H @@ -55,6 +55,14 @@ GList *finch_sound_get_profiles(void); /** + * Determine whether any sound will be played or not. + * + * @return Returns FALSE if preference is set to 'No sound', or if volume is + * set to zero. + */ +gboolean finch_sound_is_enabled(void); + +/** * Gets GNT sound UI ops. * * @return The UI operations structure.
--- a/finch/gntstatus.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/gntstatus.c Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include <gnt.h> #include <gntbox.h> @@ -299,6 +299,7 @@ { purple_notify_error(edit, _("Error"), _("Invalid title"), _("Please enter a non-empty title for the status.")); + gnt_box_give_focus_to_child(GNT_BOX(edit->window), edit->title); return; } @@ -307,6 +308,7 @@ { purple_notify_error(edit, _("Error"), _("Duplicate title"), _("Please enter a different title for the status.")); + gnt_box_give_focus_to_child(GNT_BOX(edit->window), edit->title); return; } @@ -447,6 +449,7 @@ sub->window = window = gnt_vbox_new(FALSE); gnt_box_set_toplevel(GNT_BOX(window), TRUE); gnt_box_set_title(GNT_BOX(window), _("Substatus")); /* XXX: a better title */ + gnt_box_set_alignment(GNT_BOX(window), GNT_ALIGN_MID); box = gnt_hbox_new(FALSE); gnt_box_add_widget(GNT_BOX(box), gnt_label_new(_("Account:"))); @@ -523,7 +526,7 @@ gnt_box_set_toplevel(GNT_BOX(window), TRUE); gnt_box_set_title(GNT_BOX(window), _("Edit Status")); gnt_box_set_fill(GNT_BOX(window), TRUE); - gnt_box_set_alignment(GNT_BOX(window), GNT_ALIGN_LEFT); + gnt_box_set_alignment(GNT_BOX(window), GNT_ALIGN_MID); gnt_box_set_pad(GNT_BOX(window), 0); edits = g_list_append(edits, edit);
--- a/finch/gntstatus.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/gntstatus.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _GNT_STATUS_H #define _GNT_STATUS_H
--- a/finch/gntui.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/gntui.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "internal.h" @@ -25,6 +25,7 @@ #include "gntaccount.h" #include "gntblist.h" +#include "gntcertmgr.h" #include "gntconn.h" #include "gntconv.h" #include "gntdebug.h" @@ -81,6 +82,7 @@ gnt_register_action(_("Accounts"), finch_accounts_show_all); gnt_register_action(_("Buddy List"), finch_blist_show); gnt_register_action(_("Buddy Pounces"), finch_pounces_manager_show); + gnt_register_action(_("Certificates"), finch_certmgr_show); gnt_register_action(_("Debug Window"), finch_debug_window_show); gnt_register_action(_("File Transfers"), finch_xfer_dialog_show); gnt_register_action(_("Plugins"), finch_plugins_show_all); @@ -89,8 +91,6 @@ gnt_register_action(_("Statuses"), finch_savedstatus_show_all); #ifdef STANDALONE - - finch_plugins_save_loaded(); } void gnt_ui_uninit()
--- a/finch/gntui.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/gntui.h Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _GNT_UI_H #define _GNT_UI_H
--- a/finch/libgnt/COPYING Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/COPYING Wed Sep 12 19:11:38 2007 +0000 @@ -2,7 +2,7 @@ Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. - 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. @@ -305,7 +305,7 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA Also add information on how to contact you by electronic and paper mail.
--- a/finch/libgnt/configure.ac Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/configure.ac Wed Sep 12 19:11:38 2007 +0000 @@ -24,9 +24,9 @@ # Make sure to update ../../configure.ac with libgnt version changes. # -m4_define([gnt_lt_current], [1]) +m4_define([gnt_lt_current], [2]) m4_define([gnt_major_version], [2]) -m4_define([gnt_minor_version], [1]) +m4_define([gnt_minor_version], [2]) m4_define([gnt_micro_version], [0]) m4_define([gnt_version_suffix], [devel]) m4_define([gnt_version],
--- a/finch/libgnt/gnt-skel.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gnt-skel.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "gnt-skel.h"
--- a/finch/libgnt/gnt-skel.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gnt-skel.h Wed Sep 12 19:11:38 2007 +0000 @@ -21,7 +21,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef GNT_SKEL_H
--- a/finch/libgnt/gnt.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gnt.h Wed Sep 12 19:11:38 2007 +0000 @@ -27,7 +27,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include <glib.h> @@ -37,6 +37,15 @@ #include "gntkeys.h" /** + * Get things to compile in Glib < 2.8 + */ +#if !GLIB_CHECK_VERSION(2,8,0) + #define G_PARAM_STATIC_NAME G_PARAM_PRIVATE + #define G_PARAM_STATIC_NICK G_PARAM_PRIVATE + #define G_PARAM_STATIC_BLURB G_PARAM_PRIVATE +#endif + +/** * */ void gnt_init(void);
--- a/finch/libgnt/gntbindable.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntbindable.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include <string.h>
--- a/finch/libgnt/gntbindable.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntbindable.h Wed Sep 12 19:11:38 2007 +0000 @@ -21,7 +21,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef GNT_BINDABLE_H
--- a/finch/libgnt/gntbox.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntbox.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "gntbox.h"
--- a/finch/libgnt/gntbox.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntbox.h Wed Sep 12 19:11:38 2007 +0000 @@ -21,7 +21,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef GNT_BOX_H
--- a/finch/libgnt/gntbutton.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntbutton.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include <stdlib.h>
--- a/finch/libgnt/gntbutton.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntbutton.h Wed Sep 12 19:11:38 2007 +0000 @@ -21,7 +21,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef GNT_BUTTON_H
--- a/finch/libgnt/gntcheckbox.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntcheckbox.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "gntcheckbox.h"
--- a/finch/libgnt/gntcheckbox.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntcheckbox.h Wed Sep 12 19:11:38 2007 +0000 @@ -21,7 +21,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef GNT_CHECK_BOX_H
--- a/finch/libgnt/gntclipboard.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntclipboard.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "gntclipboard.h"
--- a/finch/libgnt/gntclipboard.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntclipboard.h Wed Sep 12 19:11:38 2007 +0000 @@ -21,7 +21,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef GNT_CLIPBOARD_H
--- a/finch/libgnt/gntcolors.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntcolors.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "config.h" @@ -133,6 +133,7 @@ restore_colors(); } +#if GLIB_CHECK_VERSION(2,6,0) static int get_color(char *key) { @@ -164,7 +165,6 @@ return color; } -#if GLIB_CHECK_VERSION(2,6,0) void gnt_colors_parse(GKeyFile *kfile) { GError *error = NULL;
--- a/finch/libgnt/gntcolors.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntcolors.h Wed Sep 12 19:11:38 2007 +0000 @@ -21,7 +21,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef GNT_COLORS_H
--- a/finch/libgnt/gntcombobox.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntcombobox.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "gntbox.h"
--- a/finch/libgnt/gntcombobox.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntcombobox.h Wed Sep 12 19:11:38 2007 +0000 @@ -21,7 +21,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef GNT_COMBO_BOX_H
--- a/finch/libgnt/gntentry.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntentry.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include <ctype.h>
--- a/finch/libgnt/gntentry.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntentry.h Wed Sep 12 19:11:38 2007 +0000 @@ -21,7 +21,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef GNT_ENTRY_H
--- a/finch/libgnt/gntfilesel.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntfilesel.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "gntbutton.h" @@ -200,7 +200,7 @@ const char *tmp; tmp = sel->suggest ? sel->suggest : (const char*)gnt_tree_get_selection_data(sel->dirsonly ? GNT_TREE(sel->dirs) : GNT_TREE(sel->files)); - old = g_strdup_printf("%s%s%s", sel->current, sel->current[1] ? G_DIR_SEPARATOR_S : "", tmp ? tmp : ""); + old = g_strdup_printf("%s%s%s", SAFE(sel->current), SAFE(sel->current)[1] ? G_DIR_SEPARATOR_S : "", tmp ? tmp : ""); gnt_entry_set_text(GNT_ENTRY(sel->location), old); g_free(old); }
--- a/finch/libgnt/gntfilesel.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntfilesel.h Wed Sep 12 19:11:38 2007 +0000 @@ -21,7 +21,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef GNT_FILE_SEL_H
--- a/finch/libgnt/gntkeys.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntkeys.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "gntkeys.h"
--- a/finch/libgnt/gntkeys.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntkeys.h Wed Sep 12 19:11:38 2007 +0000 @@ -21,7 +21,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef GNT_KEYS_H
--- a/finch/libgnt/gntlabel.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntlabel.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "gntlabel.h"
--- a/finch/libgnt/gntlabel.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntlabel.h Wed Sep 12 19:11:38 2007 +0000 @@ -21,7 +21,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef GNT_LABEL_H
--- a/finch/libgnt/gntline.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntline.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "gntline.h"
--- a/finch/libgnt/gntline.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntline.h Wed Sep 12 19:11:38 2007 +0000 @@ -21,7 +21,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef GNT_LINE_H
--- a/finch/libgnt/gntmain.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntmain.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #define _GNU_SOURCE
--- a/finch/libgnt/gntmenu.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntmenu.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "gntmenu.h" @@ -221,8 +221,13 @@ { /* check for a trigger key */ GList *iter; + GList *find; GList *nth = g_list_find(menu->list, gnt_tree_get_selection_data(GNT_TREE(menu))); - GList *find = find_item_with_trigger(nth->next, NULL, trigger); + + if (nth == NULL) + return FALSE; + + find = find_item_with_trigger(nth->next, NULL, trigger); if (!find) find = find_item_with_trigger(menu->list, nth->next, trigger); if (!find)
--- a/finch/libgnt/gntmenu.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntmenu.h Wed Sep 12 19:11:38 2007 +0000 @@ -21,7 +21,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef GNT_MENU_H
--- a/finch/libgnt/gntmenuitem.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntmenuitem.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "gntmenu.h"
--- a/finch/libgnt/gntmenuitem.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntmenuitem.h Wed Sep 12 19:11:38 2007 +0000 @@ -21,7 +21,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef GNT_MENUITEM_H
--- a/finch/libgnt/gntmenuitemcheck.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntmenuitemcheck.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "gntmenuitemcheck.h"
--- a/finch/libgnt/gntmenuitemcheck.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntmenuitemcheck.h Wed Sep 12 19:11:38 2007 +0000 @@ -21,7 +21,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef GNT_MENU_ITEM_CHECK_H
--- a/finch/libgnt/gntslider.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntslider.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "gntcolors.h" @@ -125,22 +125,66 @@ step_back(GntBindable *bindable, GList *null) { GntSlider *slider = GNT_SLIDER(bindable); - if (slider->current <= slider->min) - return FALSE; gnt_slider_advance_step(slider, -1); return TRUE; } static gboolean +small_step_back(GntBindable *bindable, GList *null) +{ + GntSlider *slider = GNT_SLIDER(bindable); + gnt_slider_set_value(slider, slider->current - slider->smallstep); + return TRUE; +} + +static gboolean +large_step_back(GntBindable *bindable, GList *null) +{ + GntSlider *slider = GNT_SLIDER(bindable); + gnt_slider_set_value(slider, slider->current - slider->largestep); + return TRUE; +} + +static gboolean step_forward(GntBindable *bindable, GList *list) { GntSlider *slider = GNT_SLIDER(bindable); - if (slider->current >= slider->max) - return FALSE; gnt_slider_advance_step(slider, 1); return TRUE; } +static gboolean +small_step_forward(GntBindable *bindable, GList *null) +{ + GntSlider *slider = GNT_SLIDER(bindable); + gnt_slider_set_value(slider, slider->current + slider->smallstep); + return TRUE; +} + +static gboolean +large_step_forward(GntBindable *bindable, GList *null) +{ + GntSlider *slider = GNT_SLIDER(bindable); + gnt_slider_set_value(slider, slider->current + slider->largestep); + return TRUE; +} + +static gboolean +move_min_value(GntBindable *bindable, GList *null) +{ + GntSlider *slider = GNT_SLIDER(bindable); + gnt_slider_set_value(slider, slider->min); + return TRUE; +} + +static gboolean +move_max_value(GntBindable *bindable, GList *null) +{ + GntSlider *slider = GNT_SLIDER(bindable); + gnt_slider_set_value(slider, slider->max); + return TRUE; +} + static void gnt_slider_class_init(GntSliderClass *klass) { @@ -165,8 +209,14 @@ gnt_bindable_register_binding(bindable, "step-backward", GNT_KEY_DOWN, NULL); gnt_bindable_class_register_action(bindable, "step-forward", step_forward, GNT_KEY_RIGHT, NULL); gnt_bindable_register_binding(bindable, "step-forward", GNT_KEY_UP, NULL); - - /* XXX: how would home/end work? */ + gnt_bindable_class_register_action(bindable, "small-step-backward", small_step_back, GNT_KEY_CTRL_LEFT, NULL); + gnt_bindable_register_binding(bindable, "small-step-backward", GNT_KEY_CTRL_DOWN, NULL); + gnt_bindable_class_register_action(bindable, "small-step-forward", small_step_forward, GNT_KEY_CTRL_RIGHT, NULL); + gnt_bindable_register_binding(bindable, "small-step-forward", GNT_KEY_CTRL_UP, NULL); + gnt_bindable_class_register_action(bindable, "large-step-backward", large_step_back, GNT_KEY_PGDOWN, NULL); + gnt_bindable_class_register_action(bindable, "large-step-forward", large_step_forward, GNT_KEY_PGUP, NULL); + gnt_bindable_class_register_action(bindable, "min-value", move_min_value, GNT_KEY_HOME, NULL); + gnt_bindable_class_register_action(bindable, "max-value", move_max_value, GNT_KEY_END, NULL); gnt_style_read_actions(G_OBJECT_CLASS_TYPE(klass), GNT_BINDABLE_CLASS(klass)); } @@ -233,10 +283,14 @@ void gnt_slider_set_value(GntSlider *slider, int value) { + int old; if (slider->current == value) return; + old = slider->current; slider->current = value; sanitize_value(slider); + if (old == slider->current) + return; redraw_slider(slider); slider_value_changed(slider); } @@ -248,10 +302,7 @@ int gnt_slider_advance_step(GntSlider *slider, int steps) { - slider->current += steps * slider->step; - sanitize_value(slider); - redraw_slider(slider); - slider_value_changed(slider); + gnt_slider_set_value(slider, slider->current + steps * slider->step); return slider->current; } @@ -260,6 +311,16 @@ slider->step = step; } +void gnt_slider_set_small_step(GntSlider *slider, int step) +{ + slider->smallstep = step; +} + +void gnt_slider_set_large_step(GntSlider *slider, int step) +{ + slider->largestep = step; +} + void gnt_slider_set_range(GntSlider *slider, int max, int min) { slider->max = MAX(max, min);
--- a/finch/libgnt/gntslider.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntslider.h Wed Sep 12 19:11:38 2007 +0000 @@ -21,7 +21,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef GNT_SLIDER_H @@ -56,6 +56,8 @@ int min; /* minimum value */ int step; /* amount to change at each step */ int current; /* current value */ + int smallstep; + int largestep; }; struct _GntSliderClass @@ -103,11 +105,27 @@ * Sets the amount of change at each step. * * @param slider The slider - * @param step The amount for each ste + * @param step The amount for each step */ void gnt_slider_set_step(GntSlider *slider, int step); /** + * Sets the amount of change a small step. + * + * @param slider The slider + * @param step The amount for a small step (for the slider) + */ +void gnt_slider_set_small_step(GntSlider *slider, int step); + +/** + * Sets the amount of change a large step. + * + * @param slider The slider + * @param step The amount for a large step (for the slider) + */ +void gnt_slider_set_large_step(GntSlider *slider, int step); + +/** * Advance the slider forward or backward. * * @param slider The slider
--- a/finch/libgnt/gntstyle.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntstyle.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "gntstyle.h" @@ -55,6 +55,8 @@ if (!group) group = "general"; return g_key_file_get_value(gkfile, group, key, NULL); +#else + return NULL; #endif } @@ -93,6 +95,7 @@ return def; } +#if GLIB_CHECK_VERSION(2,6,0) static void refine(char *text) { @@ -133,6 +136,7 @@ { return (char *)gnt_key_translate(key); } +#endif void gnt_style_read_workspaces(GntWM *wm) {
--- a/finch/libgnt/gntstyle.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntstyle.h Wed Sep 12 19:11:38 2007 +0000 @@ -21,7 +21,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "gnt.h"
--- a/finch/libgnt/gnttextview.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gnttextview.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "gntstyle.h" @@ -549,7 +549,8 @@ if ((end = strchr(start, '\r')) != NULL || (end = strchr(start, '\n')) != NULL) { len = gnt_util_onscreen_width(start, end - has_scroll); - if (len >= widget->priv.width - line->length - has_scroll) { + if (widget->priv.width > 0 && + len >= widget->priv.width - line->length - has_scroll) { end = NULL; } }
--- a/finch/libgnt/gnttextview.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gnttextview.h Wed Sep 12 19:11:38 2007 +0000 @@ -21,7 +21,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef GNT_TEXT_VIEW_H
--- a/finch/libgnt/gnttree.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gnttree.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "gntmarshal.h" @@ -985,11 +985,7 @@ g_param_spec_int("columns", "Columns", "Number of columns in the tree.", 1, G_MAXINT, 1, -#if GLIB_CHECK_VERSION(2,8,0) G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB -#else - G_PARAM_READWRITE|G_PARAM_PRIVATE -#endif ) );
--- a/finch/libgnt/gnttree.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gnttree.h Wed Sep 12 19:11:38 2007 +0000 @@ -21,7 +21,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef GNT_TREE_H
--- a/finch/libgnt/gntutils.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntutils.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "gntbutton.h" @@ -376,6 +376,101 @@ #endif } +#ifndef NO_LIBXML +static void +util_parse_html_to_tv(xmlNode *node, GntTextView *tv, GntTextFormatFlags flag) +{ + const char *name; + char *content; + xmlNode *ch; + gboolean processed = FALSE; + char *url = NULL; + gboolean insert_nl_s = FALSE, insert_nl_e = FALSE; + + if (node == NULL || node->name == NULL || node->type != XML_ELEMENT_NODE) + return; + + name = (char*)node->name; + if (g_ascii_strcasecmp(name, "b") == 0 || + g_ascii_strcasecmp(name, "strong") == 0 || + g_ascii_strcasecmp(name, "i") == 0 || + g_ascii_strcasecmp(name, "blockquote") == 0) { + flag |= GNT_TEXT_FLAG_BOLD; + } else if (g_ascii_strcasecmp(name, "u") == 0) { + flag |= GNT_TEXT_FLAG_UNDERLINE; + } else if (g_ascii_strcasecmp(name, "br") == 0) { + insert_nl_e = TRUE; + } else if (g_ascii_strcasecmp(name, "a") == 0) { + flag |= GNT_TEXT_FLAG_UNDERLINE; + url = (char *)xmlGetProp(node, (xmlChar*)"href"); + } else if (g_ascii_strcasecmp(name, "h1") == 0 || + g_ascii_strcasecmp(name, "h2") == 0 || + g_ascii_strcasecmp(name, "h3") == 0 || + g_ascii_strcasecmp(name, "h4") == 0 || + g_ascii_strcasecmp(name, "h5") == 0 || + g_ascii_strcasecmp(name, "h6") == 0) { + insert_nl_s = TRUE; + insert_nl_e = TRUE; + } else if (g_ascii_strcasecmp(name, "title") == 0) { + insert_nl_s = TRUE; + insert_nl_e = TRUE; + flag |= GNT_TEXT_FLAG_BOLD | GNT_TEXT_FLAG_UNDERLINE; + } else { + /* XXX: Process other possible tags */ + } + + if (insert_nl_s) + gnt_text_view_append_text_with_flags(tv, "\n", flag); + + for (ch = node->children; ch; ch = ch->next) { + if (ch->type == XML_ELEMENT_NODE) { + processed = TRUE; + util_parse_html_to_tv(ch, tv, flag); + } + } + + if (!processed) { + content = (char*)xmlNodeGetContent(node); + gnt_text_view_append_text_with_flags(tv, content, flag); + xmlFree(content); + } + + if (url) { + char *href = g_strdup_printf(" (%s)", url); + gnt_text_view_append_text_with_flags(tv, href, flag); + g_free(href); + xmlFree(url); + } + + if (insert_nl_e) + gnt_text_view_append_text_with_flags(tv, "\n", flag); +} +#endif + +gboolean gnt_util_parse_xhtml_to_textview(const char *string, GntTextView *tv) +{ +#ifdef NO_LIBXML + return FALSE; +#else + xmlParserCtxtPtr ctxt; + xmlDocPtr doc; + xmlNodePtr node; + GntTextFormatFlags flag = GNT_TEXT_FLAG_NORMAL; + gboolean ret = FALSE; + + ctxt = xmlNewParserCtxt(); + doc = xmlCtxtReadDoc(ctxt, (xmlChar*)string, NULL, NULL, XML_PARSE_NOBLANKS | XML_PARSE_RECOVER); + if (doc) { + node = xmlDocGetRootElement(doc); + util_parse_html_to_tv(node, tv, flag); + xmlFreeDoc(doc); + ret = TRUE; + } + xmlCleanupParser(); + return ret; +#endif +} + /* Setup trigger widget */ typedef struct { char *text; @@ -408,4 +503,3 @@ g_signal_connect(G_OBJECT(wid), "key_pressed", G_CALLBACK(key_pressed), tb); g_signal_connect_swapped(G_OBJECT(button), "destroy", G_CALLBACK(free_trigger_button), tb); } -
--- a/finch/libgnt/gntutils.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntutils.h Wed Sep 12 19:11:38 2007 +0000 @@ -21,12 +21,13 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include <glib.h> #include "gnt.h" +#include "gnttextview.h" #include "gntwidget.h" typedef gpointer (*GDupFunc)(gconstpointer data); @@ -132,6 +133,16 @@ void gnt_util_parse_widgets(const char *string, int num, ...); /** + * Parse an XHTML string and add it in a GntTextView with + * appropriate text flags. + * + * @param string The XHTML string + * @param tv The GntTextView + * @return @c TRUE if the string was added to the textview properly, @c FALSE otherwise. + */ +gboolean gnt_util_parse_xhtml_to_textview(const char *string, GntTextView *tv); + +/** * Make some keypress activate a button when some key is pressed with 'wid' in focus. * * @param widget The widget
--- a/finch/libgnt/gntwidget.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntwidget.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ /* Stuff brutally ripped from Gflib */
--- a/finch/libgnt/gntwidget.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntwidget.h Wed Sep 12 19:11:38 2007 +0000 @@ -21,7 +21,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef GNT_WIDGET_H
--- a/finch/libgnt/gntwindow.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntwindow.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "gntstyle.h"
--- a/finch/libgnt/gntwindow.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntwindow.h Wed Sep 12 19:11:38 2007 +0000 @@ -21,7 +21,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef GNT_WINDOW_H
--- a/finch/libgnt/gntwm.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntwm.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #define _GNU_SOURCE @@ -738,7 +738,7 @@ print = ch; #ifndef NO_WIDECHAR if (wch.chars[0] > 255) { - snprintf(unicode, sizeof(unicode), "&#x%x;", wch.chars[0]); + snprintf(unicode, sizeof(unicode), "&#x%x;", (unsigned int)wch.chars[0]); print = unicode; } #endif
--- a/finch/libgnt/gntwm.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntwm.h Wed Sep 12 19:11:38 2007 +0000 @@ -21,7 +21,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef GNTWM_H
--- a/finch/libgnt/gntws.h Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/gntws.h Wed Sep 12 19:11:38 2007 +0000 @@ -21,7 +21,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef GNTWS_H
--- a/finch/libgnt/pygnt/dbus-gnt Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/pygnt/dbus-gnt Wed Sep 12 19:11:38 2007 +0000 @@ -13,7 +13,7 @@ import gnt import sys -from time import strftime +import time convwins = {} @@ -27,19 +27,26 @@ # if a conv window is closed, then reopened, this thing crashes convwins[key] = None -def wrote_msg(account, who, msg, conv, flags): - stuff = show_conversation(conv) +def add_message(conv, who, msg, flags, timestamp): + stuff = show_conversation(conv, False) tv = stuff[1] tv.append_text_with_flags("\n", 0) - tv.append_text_with_flags(strftime("(%X) "), 8) + if timestamp: + tv.append_text_with_flags(time.strftime("(%X) ", time.localtime(timestamp)), 8) + else: + tv.append_text_with_flags(time.strftime("(%X) "), 8) if flags & 3: tv.append_text_with_flags(who + ": ", 1) + msg = purple.PurpleMarkupStripHtml(msg) tv.append_text_with_flags(msg, 0) stuff[0].set_urgent() else: tv.append_text_with_flags(msg, 8) tv.scroll(0) +def wrote_msg(account, who, msg, conv, flags): + add_message(conv, who, msg, flags, None) + bus = dbus.SessionBus() obj = bus.get_object("im.pidgin.purple.PurpleService", "/im/pidgin/purple/PurpleObject") purple = dbus.Interface(obj, "im.pidgin.purple.PurpleInterface") @@ -79,7 +86,7 @@ def conv_window_destroyed(win, key): del convwins[key] -def show_conversation(conv): +def show_conversation(conv, history): key = get_dict_key(conv) if key in convwins: return convwins[key] @@ -96,9 +103,21 @@ vbox.add_widget(entry) entry.connect("key_pressed", send_im_cb, conv) tv.clear() + tv.attach_scroll_widget(entry) win.show() convwins[key] = [win, tv, entry] win.connect("destroy", conv_window_destroyed, key) + + if history: + msgs = purple.PurpleConversationGetMessageHistory(conv) + msgs.reverse() + for msg in msgs: + who = purple.PurpleConversationMessageGetSender(msg) + what = purple.PurpleConversationMessageGetMessage(msg) + flags = purple.PurpleConversationMessageGetFlags(msg) + when = purple.PurpleConversationMessageGetTimestamp(msg) + add_message(conv, who, what, flags, when) + return convwins[key] def show_buddylist(): @@ -127,7 +146,7 @@ convs = purple.PurpleGetConversations() for conv in convs: - show_conversation(conv) + show_conversation(conv, True) gnt.gnt_main()
--- a/finch/libgnt/pygnt/example/rss/gnthtml.py Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/pygnt/example/rss/gnthtml.py Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ You should have received a copy of the GNU Lesser General Public License along with this application; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA """
--- a/finch/libgnt/pygnt/example/rss/gntrss-ui.py Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/pygnt/example/rss/gntrss-ui.py Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ You should have received a copy of the GNU Lesser General Public License along with this application; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA """ @@ -54,7 +54,7 @@ } __gntbindings__ = { - 'jump-next-unread' : ('jump_next_unread', 'J') + 'jump-next-unread' : ('jump_next_unread', 'n') } def jump_next_unread(self, null):
--- a/finch/libgnt/pygnt/example/rss/gntrss.py Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/pygnt/example/rss/gntrss.py Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ You should have received a copy of the GNU Lesser General Public License along with this application; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA """ @@ -61,6 +61,9 @@ self.parent = parent self.unread = True + def __del__(self): + pass + def remove(self): self.emit('delete', self.parent) if self.unread: @@ -126,6 +129,9 @@ self.pending = False self._refresh = {'time' : 30, 'id' : 0} + def __del__(self): + pass + def do_set_property(self, property, value): if property.name == 'link': self.link = value
--- a/finch/libgnt/pygnt/gendef.sh Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/pygnt/gendef.sh Wed Sep 12 19:11:38 2007 +0000 @@ -31,7 +31,9 @@ rm -f gnt.def for file in $FILES do + echo -n "Generating definitions for ${file} ... " python /usr/share/pygtk/2.0/codegen/h2def.py ../$file >> gnt.def + echo "Done" done # Remove the definitions about the enums
--- a/finch/libgnt/pygnt/gntbox.override Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/pygnt/gntbox.override Wed Sep 12 19:11:38 2007 +0000 @@ -16,7 +16,7 @@ * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 * USA */ %%
--- a/finch/libgnt/pygnt/gntfilesel.override Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/pygnt/gntfilesel.override Wed Sep 12 19:11:38 2007 +0000 @@ -16,7 +16,7 @@ * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 * USA */ %%
--- a/finch/libgnt/pygnt/gnttree.override Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/pygnt/gnttree.override Wed Sep 12 19:11:38 2007 +0000 @@ -16,7 +16,7 @@ * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 * USA */ %% @@ -51,7 +51,6 @@ while (list) { PyObject *obj = list->data; PyList_Append(py_list, obj); - Py_DECREF(obj); list = list->next; } return py_list; @@ -89,6 +88,11 @@ Py_DECREF(item); } + if (parent == Py_None) + parent = NULL; + if (bigbro == Py_None) + bigbro = NULL; + list = g_list_reverse(list); row = gnt_tree_create_row_from_list(GNT_TREE(self->obj), list); gnt_tree_add_row_after(GNT_TREE(self->obj),
--- a/finch/libgnt/pygnt/gntwidget.override Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/pygnt/gntwidget.override Wed Sep 12 19:11:38 2007 +0000 @@ -16,7 +16,7 @@ * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 * USA */ %%
--- a/finch/libgnt/pygnt/test.py Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/pygnt/test.py Wed Sep 12 19:11:38 2007 +0000 @@ -16,6 +16,9 @@ self.__gobject_init__() self.set_property(type, value) + def __del__(self): + pass + def do_set_property(self, pspec, value): if pspec.name == 'string': self.string = value
--- a/finch/libgnt/test/tv.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/libgnt/test/tv.c Wed Sep 12 19:11:38 2007 +0000 @@ -5,6 +5,7 @@ #include "gntbox.h" #include "gntentry.h" #include "gnttextview.h" +#include "gntutils.h" static gboolean key_pressed(GntWidget *w, const char *key, GntWidget *view) @@ -117,6 +118,8 @@ gnt_text_view_append_text_with_flags(GNT_TEXT_VIEW(view), "plugins: ", GNT_TEXT_FLAG_BOLD); gnt_text_view_append_text_with_flags(GNT_TEXT_VIEW(view), "this is the 4th line\n", GNT_TEXT_FLAG_NORMAL); + gnt_util_parse_xhtml_to_textview("<p><b>Ohoy hoy!!</b><br/><p>I think this is going to</p> <u> WORK!!! </u><a href='www.google.com'>check this out!!</a></p>", GNT_TEXT_VIEW(view)); + #ifdef STANDALONE gnt_main();
--- a/finch/plugins/gntclipboard.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/plugins/gntclipboard.c Wed Sep 12 19:11:38 2007 +0000 @@ -15,7 +15,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ @@ -38,10 +38,12 @@ #include <plugin.h> #include <version.h> #include <debug.h> +#include <notify.h> #include <gntwm.h> #include <gntplugin.h> +#ifdef HAVE_X11 static pid_t child = 0; static gulong sig_handle; @@ -49,7 +51,6 @@ static void set_clip(gchar *string) { -#ifdef HAVE_X11 Window w; XEvent e, respond; XSelectionRequestEvent *req; @@ -89,14 +90,12 @@ return; } } -#endif return; } static void clipboard_changed(GntWM *wm, gchar *string) { -#ifdef HAVE_X11 if (child) { kill(child, SIGTERM); } @@ -104,8 +103,8 @@ set_clip(string); _exit(0); } +} #endif -} static gboolean plugin_load(PurplePlugin *plugin) @@ -113,25 +112,35 @@ #ifdef HAVE_X11 if (!XOpenDisplay(NULL)) { purple_debug_warning("gntclipboard", "Couldn't find X display\n"); + purple_notify_error(NULL, _("Error"), _("Error loading the plugin."), + _("Couldn't find X display")); return FALSE; } -#endif if (!getenv("WINDOWID")) { purple_debug_warning("gntclipboard", "Couldn't find window\n"); + purple_notify_error(NULL, _("Error"), _("Error loading the plugin."), + _("Couldn't find window")); return FALSE; } sig_handle = g_signal_connect(G_OBJECT(gnt_get_clipboard()), "clipboard_changed", G_CALLBACK(clipboard_changed), NULL); return TRUE; +#else + purple_notify_error(NULL, _("Error"), _("Error loading the plugin."), + _("This plugin cannot be loaded because it was not built with X11 support.")); + return FALSE; +#endif } static gboolean plugin_unload(PurplePlugin *plugin) { +#ifdef HAVE_X11 if (child) { kill(child, SIGTERM); child = 0; } g_signal_handler_disconnect(G_OBJECT(gnt_get_clipboard()), sig_handle); +#endif return TRUE; }
--- a/finch/plugins/gntgf.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/plugins/gntgf.c Wed Sep 12 19:11:38 2007 +0000 @@ -15,7 +15,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
--- a/finch/plugins/gnthistory.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/plugins/gnthistory.c Wed Sep 12 19:11:38 2007 +0000 @@ -15,7 +15,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ /* Ripped from gtk/plugins/history.c */
--- a/finch/plugins/lastlog.c Sun Aug 19 13:40:34 2007 +0000 +++ b/finch/plugins/lastlog.c Wed Sep 12 19:11:38 2007 +0000 @@ -15,7 +15,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
--- a/libpurple/Makefile.am Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/Makefile.am Wed Sep 12 19:11:38 2007 +0000 @@ -36,6 +36,7 @@ accountopt.c \ blist.c \ buddyicon.c \ + certificate.c \ cipher.c \ circbuffer.c \ cmds.c \ @@ -85,6 +86,7 @@ accountopt.h \ blist.h \ buddyicon.h \ + certificate.h \ cipher.h \ circbuffer.h \ cmds.h \
--- a/libpurple/Makefile.mingw Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/Makefile.mingw Wed Sep 12 19:11:38 2007 +0000 @@ -33,6 +33,7 @@ accountopt.c \ blist.c \ buddyicon.c \ + certificate.c \ cipher.c \ cmds.c \ connection.c \
--- a/libpurple/account.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/account.c Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "internal.h" #include "account.h" @@ -913,6 +913,15 @@ } void +purple_account_set_register_callback(PurpleAccount *account, PurpleAccountRegistrationCb cb, void *user_data) +{ + g_return_if_fail(account != NULL); + + account->registration_cb = cb; + account->registration_cb_user_data = user_data; +} + +void purple_account_register(PurpleAccount *account) { g_return_if_fail(account != NULL); @@ -923,6 +932,17 @@ purple_connection_new(account, TRUE, purple_account_get_password(account)); } +void +purple_account_unregister(PurpleAccount *account, PurpleAccountUnregistrationCb cb, void *user_data) +{ + g_return_if_fail(account != NULL); + + purple_debug_info("account", "Unregistering account %s\n", + purple_account_get_username(account)); + + purple_connection_new_unregister(account, purple_account_get_password(account), cb, user_data); +} + static void request_password_ok_cb(PurpleAccount *account, PurpleRequestFields *fields) { @@ -1720,15 +1740,6 @@ purple_account_get_protocol_id(const PurpleAccount *account) { g_return_val_if_fail(account != NULL, NULL); - /* - * HACK by Seanegan - */ - if (!strcmp(account->protocol_id, "prpl-oscar")) { - if (isdigit(account->username[0])) - return "prpl-icq"; - else - return "prpl-aim"; - } return account->protocol_id; }
--- a/libpurple/account.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/account.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * * @see @ref account-signals */ @@ -36,6 +36,8 @@ typedef gboolean (*PurpleFilterAccountFunc)(PurpleAccount *account); typedef void (*PurpleAccountRequestAuthorizationCb)(void *); +typedef void (*PurpleAccountRegistrationCb)(PurpleAccount *account, gboolean succeeded, void *user_data); +typedef void (*PurpleAccountUnregistrationCb)(PurpleAccount *account, gboolean succeeded, void *user_data); #include "connection.h" #include "log.h" @@ -136,6 +138,8 @@ PurpleLog *system_log; /**< The system log */ void *ui_data; /**< The UI can put data here. */ + PurpleAccountRegistrationCb registration_cb; + void *registration_cb_user_data; }; #ifdef __cplusplus @@ -172,6 +176,15 @@ void purple_account_connect(PurpleAccount *account); /** + * Sets the callback for successful registration. + * + * @param account The account for which this callback should be used + * @param cb The callback + * @param user_data The user data passed to the callback + */ +void purple_account_set_register_callback(PurpleAccount *account, PurpleAccountRegistrationCb cb, void *user_data); + +/** * Registers an account. * * @param account The account to register. @@ -179,6 +192,15 @@ void purple_account_register(PurpleAccount *account); /** + * Unregisters an account (deleting it from the server). + * + * @param account The account to unregister. + * @param cb Optional callback to be called when unregistration is complete + * @param user_data user data to pass to the callback + */ +void purple_account_unregister(PurpleAccount *account, PurpleAccountUnregistrationCb cb, void *user_data); + +/** * Disconnects from an account. * * @param account The account to disconnect from.
--- a/libpurple/accountopt.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/accountopt.c Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "internal.h"
--- a/libpurple/accountopt.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/accountopt.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _PURPLE_ACCOUNTOPT_H_ #define _PURPLE_ACCOUNTOPT_H_
--- a/libpurple/blist.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/blist.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */ #include "internal.h" @@ -1190,6 +1190,12 @@ group = purple_group_new(_("Chats")); purple_blist_add_group(group, purple_blist_get_last_sibling(purplebuddylist->root)); + } else { + /* Add group to blist if isn't already on it. Fixes #2752. */ + if (!purple_find_group(group->name)) { + purple_blist_add_group(group, + purple_blist_get_last_sibling(purplebuddylist->root)); + } } } else { group = (PurpleGroup*)node->parent; @@ -1284,6 +1290,12 @@ g = (PurpleGroup *)((PurpleBlistNode *)c)->parent; } else { if (group) { + /* Add chat to blist if isn't already on it. Fixes #2752. */ + if (!purple_find_group(group->name)) { + purple_blist_add_group(group, + purple_blist_get_last_sibling(purplebuddylist->root)); + } + g = group; } else { g = purple_group_new(_("Buddies"));
--- a/libpurple/blist.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/blist.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * * @see @ref blist-signals */
--- a/libpurple/buddyicon.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/buddyicon.c Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "internal.h" #include "buddyicon.h"
--- a/libpurple/buddyicon.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/buddyicon.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _PURPLE_BUDDYICON_H_ #define _PURPLE_BUDDYICON_H_
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/certificate.c Wed Sep 12 19:11:38 2007 +0000 @@ -0,0 +1,1903 @@ +/** + * @file certificate.c Public-Key Certificate API + * @ingroup core + */ + +/* + * + * purple + * + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * 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 "internal.h" +#include "certificate.h" +#include "dbus-maybe.h" +#include "debug.h" +#include "request.h" +#include "signals.h" +#include "util.h" + +/** List holding pointers to all registered certificate schemes */ +static GList *cert_schemes = NULL; +/** List of registered Verifiers */ +static GList *cert_verifiers = NULL; +/** List of registered Pools */ +static GList *cert_pools = NULL; + +void +purple_certificate_verify (PurpleCertificateVerifier *verifier, + const gchar *subject_name, GList *cert_chain, + PurpleCertificateVerifiedCallback cb, + gpointer cb_data) +{ + PurpleCertificateVerificationRequest *vrq; + PurpleCertificateScheme *scheme; + + g_return_if_fail(subject_name != NULL); + /* If you don't have a cert to check, why are you requesting that it + be verified? */ + g_return_if_fail(cert_chain != NULL); + g_return_if_fail(cb != NULL); + + /* Look up the CertificateScheme */ + scheme = purple_certificate_find_scheme(verifier->scheme_name); + g_return_if_fail(scheme); + + /* Check that at least the first cert in the chain matches the + Verifier scheme */ + g_return_if_fail(scheme == + ((PurpleCertificate *) (cert_chain->data))->scheme); + + /* Construct and fill in the request fields */ + vrq = g_new0(PurpleCertificateVerificationRequest, 1); + vrq->verifier = verifier; + vrq->scheme = scheme; + vrq->subject_name = g_strdup(subject_name); + vrq->cert_chain = purple_certificate_copy_list(cert_chain); + vrq->cb = cb; + vrq->cb_data = cb_data; + + /* Initiate verification */ + (verifier->start_verification)(vrq); +} + +void +purple_certificate_verify_complete(PurpleCertificateVerificationRequest *vrq, + PurpleCertificateVerificationStatus st) +{ + PurpleCertificateVerifier *vr; + + g_return_if_fail(vrq); + + /* Pass the results on to the request's callback */ + (vrq->cb)(st, vrq->cb_data); + + /* And now to eliminate the request */ + /* Fetch the Verifier responsible... */ + vr = vrq->verifier; + /* ...and order it to KILL */ + (vr->destroy_request)(vrq); + + /* Now the internals have been cleaned up, so clean up the libpurple- + created elements */ + g_free(vrq->subject_name); + purple_certificate_destroy_list(vrq->cert_chain); + + /* A structure born + * to much ado + * and with so much within. + * It reaches now + * its quiet end. */ + g_free(vrq); +} + + +PurpleCertificate * +purple_certificate_copy(PurpleCertificate *crt) +{ + g_return_val_if_fail(crt, NULL); + g_return_val_if_fail(crt->scheme, NULL); + g_return_val_if_fail(crt->scheme->copy_certificate, NULL); + + return (crt->scheme->copy_certificate)(crt); +} + +GList * +purple_certificate_copy_list(GList *crt_list) +{ + GList *new, *l; + + /* First, make a shallow copy of the list */ + new = g_list_copy(crt_list); + + /* Now go through and actually duplicate each certificate */ + for (l = new; l; l = l->next) { + l->data = purple_certificate_copy(l->data); + } + + return new; +} + +void +purple_certificate_destroy (PurpleCertificate *crt) +{ + PurpleCertificateScheme *scheme; + + if (NULL == crt) return; + + scheme = crt->scheme; + + (scheme->destroy_certificate)(crt); +} + +void +purple_certificate_destroy_list (GList * crt_list) +{ + PurpleCertificate *crt; + GList *l; + + for (l=crt_list; l; l = l->next) { + crt = (PurpleCertificate *) l->data; + purple_certificate_destroy(crt); + } + + g_list_free(crt_list); +} + +gboolean +purple_certificate_signed_by(PurpleCertificate *crt, PurpleCertificate *issuer) +{ + PurpleCertificateScheme *scheme; + + g_return_val_if_fail(crt, FALSE); + g_return_val_if_fail(issuer, FALSE); + + scheme = crt->scheme; + g_return_val_if_fail(scheme, FALSE); + /* We can't compare two certs of unrelated schemes, obviously */ + g_return_val_if_fail(issuer->scheme == scheme, FALSE); + + return (scheme->signed_by)(crt, issuer); +} + +gboolean +purple_certificate_check_signature_chain(GList *chain) +{ + GList *cur; + PurpleCertificate *crt, *issuer; + gchar *uid; + + g_return_val_if_fail(chain, FALSE); + + uid = purple_certificate_get_unique_id((PurpleCertificate *) chain->data); + purple_debug_info("certificate", + "Checking signature chain for uid=%s\n", + uid); + g_free(uid); + + /* If this is a single-certificate chain, say that it is valid */ + if (chain->next == NULL) { + purple_debug_info("certificate", + "...Singleton. We'll say it's valid.\n"); + return TRUE; + } + + /* Load crt with the first certificate */ + crt = (PurpleCertificate *)(chain->data); + /* And start with the second certificate in the chain */ + for ( cur = chain->next; cur; cur = cur->next ) { + + issuer = (PurpleCertificate *)(cur->data); + + /* Check the signature for this link */ + if (! purple_certificate_signed_by(crt, issuer) ) { + uid = purple_certificate_get_unique_id(issuer); + purple_debug_info("certificate", + "...Bad or missing signature by %s\nChain is INVALID\n", + uid); + g_free(uid); + + return FALSE; + } + + uid = purple_certificate_get_unique_id(issuer); + purple_debug_info("certificate", + "...Good signature by %s\n", + uid); + g_free(uid); + + /* The issuer is now the next crt whose signature is to be + checked */ + crt = issuer; + } + + /* If control reaches this point, the chain is valid */ + purple_debug_info("certificate", "Chain is VALID\n"); + return TRUE; +} + +PurpleCertificate * +purple_certificate_import(PurpleCertificateScheme *scheme, const gchar *filename) +{ + g_return_val_if_fail(scheme, NULL); + g_return_val_if_fail(scheme->import_certificate, NULL); + g_return_val_if_fail(filename, NULL); + + return (scheme->import_certificate)(filename); +} + +gboolean +purple_certificate_export(const gchar *filename, PurpleCertificate *crt) +{ + PurpleCertificateScheme *scheme; + + g_return_val_if_fail(filename, FALSE); + g_return_val_if_fail(crt, FALSE); + g_return_val_if_fail(crt->scheme, FALSE); + + scheme = crt->scheme; + g_return_val_if_fail(scheme->export_certificate, FALSE); + + return (scheme->export_certificate)(filename, crt); +} + +GByteArray * +purple_certificate_get_fingerprint_sha1(PurpleCertificate *crt) +{ + PurpleCertificateScheme *scheme; + GByteArray *fpr; + + g_return_val_if_fail(crt, NULL); + g_return_val_if_fail(crt->scheme, NULL); + + scheme = crt->scheme; + + g_return_val_if_fail(scheme->get_fingerprint_sha1, NULL); + + fpr = (scheme->get_fingerprint_sha1)(crt); + + return fpr; +} + +gchar * +purple_certificate_get_unique_id(PurpleCertificate *crt) +{ + g_return_val_if_fail(crt, NULL); + g_return_val_if_fail(crt->scheme, NULL); + g_return_val_if_fail(crt->scheme->get_unique_id, NULL); + + return (crt->scheme->get_unique_id)(crt); +} + +gchar * +purple_certificate_get_issuer_unique_id(PurpleCertificate *crt) +{ + g_return_val_if_fail(crt, NULL); + g_return_val_if_fail(crt->scheme, NULL); + g_return_val_if_fail(crt->scheme->get_issuer_unique_id, NULL); + + return (crt->scheme->get_issuer_unique_id)(crt); +} + +gchar * +purple_certificate_get_subject_name(PurpleCertificate *crt) +{ + PurpleCertificateScheme *scheme; + gchar *subject_name; + + g_return_val_if_fail(crt, NULL); + g_return_val_if_fail(crt->scheme, NULL); + + scheme = crt->scheme; + + g_return_val_if_fail(scheme->get_subject_name, NULL); + + subject_name = (scheme->get_subject_name)(crt); + + return subject_name; +} + +gboolean +purple_certificate_check_subject_name(PurpleCertificate *crt, const gchar *name) +{ + PurpleCertificateScheme *scheme; + + g_return_val_if_fail(crt, FALSE); + g_return_val_if_fail(crt->scheme, FALSE); + g_return_val_if_fail(name, FALSE); + + scheme = crt->scheme; + + /* TODO: Instead of failing, maybe use get_subject_name and strcmp? */ + g_return_val_if_fail(scheme->check_subject_name, FALSE); + + return (scheme->check_subject_name)(crt, name); +} + +gboolean +purple_certificate_get_times(PurpleCertificate *crt, time_t *activation, time_t *expiration) +{ + PurpleCertificateScheme *scheme; + + g_return_val_if_fail(crt, FALSE); + + scheme = crt->scheme; + + g_return_val_if_fail(scheme, FALSE); + + /* If both provided references are NULL, what are you doing calling + this? */ + g_return_val_if_fail( (activation != NULL) || (expiration != NULL), FALSE); + + /* Throw the request on down to the certscheme */ + return (scheme->get_times)(crt, activation, expiration); +} + + +gchar * +purple_certificate_pool_mkpath(PurpleCertificatePool *pool, const gchar *id) +{ + gchar *path; + gchar *esc_scheme_name, *esc_name, *esc_id; + + g_return_val_if_fail(pool, NULL); + g_return_val_if_fail(pool->scheme_name, NULL); + g_return_val_if_fail(pool->name, NULL); + + /* Escape all the elements for filesystem-friendliness */ + esc_scheme_name = pool ? g_strdup(purple_escape_filename(pool->scheme_name)) : NULL; + esc_name = pool ? g_strdup(purple_escape_filename(pool->name)) : NULL; + esc_id = id ? g_strdup(purple_escape_filename(id)) : NULL; + + path = g_build_filename(purple_user_dir(), + "certificates", /* TODO: constantize this? */ + esc_scheme_name, + esc_name, + esc_id, + NULL); + + g_free(esc_scheme_name); + g_free(esc_name); + g_free(esc_id); + return path; +} + +gboolean +purple_certificate_pool_usable(PurpleCertificatePool *pool) +{ + g_return_val_if_fail(pool, FALSE); + g_return_val_if_fail(pool->scheme_name, FALSE); + + /* Check that the pool's scheme is loaded */ + if (purple_certificate_find_scheme(pool->scheme_name) == NULL) { + return FALSE; + } + + return TRUE; +} + +PurpleCertificateScheme * +purple_certificate_pool_get_scheme(PurpleCertificatePool *pool) +{ + g_return_val_if_fail(pool, NULL); + g_return_val_if_fail(pool->scheme_name, NULL); + + return purple_certificate_find_scheme(pool->scheme_name); +} + +gboolean +purple_certificate_pool_contains(PurpleCertificatePool *pool, const gchar *id) +{ + g_return_val_if_fail(pool, FALSE); + g_return_val_if_fail(id, FALSE); + g_return_val_if_fail(pool->cert_in_pool, FALSE); + + return (pool->cert_in_pool)(id); +} + +PurpleCertificate * +purple_certificate_pool_retrieve(PurpleCertificatePool *pool, const gchar *id) +{ + g_return_val_if_fail(pool, NULL); + g_return_val_if_fail(id, NULL); + g_return_val_if_fail(pool->get_cert, NULL); + + return (pool->get_cert)(id); +} + +gboolean +purple_certificate_pool_store(PurpleCertificatePool *pool, const gchar *id, PurpleCertificate *crt) +{ + gboolean ret = FALSE; + + g_return_val_if_fail(pool, FALSE); + g_return_val_if_fail(id, FALSE); + g_return_val_if_fail(pool->put_cert, FALSE); + + /* Whether crt->scheme matches find_scheme(pool->scheme_name) is not + relevant... I think... */ + g_return_val_if_fail( + g_ascii_strcasecmp(pool->scheme_name, crt->scheme->name) == 0, + FALSE); + + ret = (pool->put_cert)(id, crt); + + /* Signal that the certificate was stored if success*/ + if (ret) { + purple_signal_emit(pool, "certificate-stored", + pool, id); + } + + return ret; +} + +gboolean +purple_certificate_pool_delete(PurpleCertificatePool *pool, const gchar *id) +{ + gboolean ret = FALSE; + + g_return_val_if_fail(pool, FALSE); + g_return_val_if_fail(id, FALSE); + g_return_val_if_fail(pool->delete_cert, FALSE); + + ret = (pool->delete_cert)(id); + + /* Signal that the certificate was deleted if success */ + if (ret) { + purple_signal_emit(pool, "certificate-deleted", + pool, id); + } + + return ret; +} + +GList * +purple_certificate_pool_get_idlist(PurpleCertificatePool *pool) +{ + g_return_val_if_fail(pool, NULL); + g_return_val_if_fail(pool->get_idlist, NULL); + + return (pool->get_idlist)(); +} + +void +purple_certificate_pool_destroy_idlist(GList *idlist) +{ + GList *l; + + /* Iterate through and free them strings */ + for ( l = idlist; l; l = l->next ) { + g_free(l->data); + } + + g_list_free(idlist); +} + + +/****************************************************************************/ +/* Builtin Verifiers, Pools, etc. */ +/****************************************************************************/ + +static void +x509_singleuse_verify_cb (PurpleCertificateVerificationRequest *vrq, gint id) +{ + g_return_if_fail(vrq); + + purple_debug_info("certificate/x509_singleuse", + "VRQ on cert from %s gave %d\n", + vrq->subject_name, id); + + /* Signal what happened back to the caller */ + if (1 == id) { + /* Accepted! */ + purple_certificate_verify_complete(vrq, + PURPLE_CERTIFICATE_VALID); + } else { + /* Not accepted */ + purple_certificate_verify_complete(vrq, + PURPLE_CERTIFICATE_INVALID); + + } +} + +static void +x509_singleuse_start_verify (PurpleCertificateVerificationRequest *vrq) +{ + gchar *sha_asc; + GByteArray *sha_bin; + gchar *cn; + const gchar *cn_match; + gchar *primary, *secondary; + PurpleCertificate *crt = (PurpleCertificate *) vrq->cert_chain->data; + + /* Pull out the SHA1 checksum */ + sha_bin = purple_certificate_get_fingerprint_sha1(crt); + /* Now decode it for display */ + sha_asc = purple_base16_encode_chunked(sha_bin->data, + sha_bin->len); + + /* Get the cert Common Name */ + cn = purple_certificate_get_subject_name(crt); + + /* Determine whether the name matches */ + if (purple_certificate_check_subject_name(crt, vrq->subject_name)) { + cn_match = _(""); + } else { + cn_match = _("(DOES NOT MATCH)"); + } + + /* Make messages */ + primary = g_strdup_printf(_("%s has presented the following certificate for just-this-once use:"), vrq->subject_name); + secondary = g_strdup_printf(_("Common name: %s %s\nFingerprint (SHA1): %s"), cn, cn_match, sha_asc); + + /* Make a semi-pretty display */ + purple_request_accept_cancel( + vrq->cb_data, /* TODO: Find what the handle ought to be */ + _("Single-use Certificate Verification"), + primary, + secondary, + 1, /* Accept by default */ + NULL, /* No account */ + NULL, /* No other user */ + NULL, /* No associated conversation */ + vrq, + x509_singleuse_verify_cb, + x509_singleuse_verify_cb ); + + /* Cleanup */ + g_free(primary); + g_free(secondary); + g_free(sha_asc); + g_byte_array_free(sha_bin, TRUE); +} + +static void +x509_singleuse_destroy_request (PurpleCertificateVerificationRequest *vrq) +{ + /* I don't do anything! */ +} + +PurpleCertificateVerifier x509_singleuse = { + "x509", /* Scheme name */ + "singleuse", /* Verifier name */ + x509_singleuse_start_verify, /* start_verification function */ + x509_singleuse_destroy_request, /* Request cleanup operation */ + + NULL, + NULL, + NULL, + NULL +}; + + + +/***** X.509 Certificate Authority pool, keyed by Distinguished Name *****/ +/* This is implemented in what may be the most inefficient and bugprone way + possible; however, future optimizations should not be difficult. */ + +static PurpleCertificatePool x509_ca; + +/** Holds a key-value pair for quickish certificate lookup */ +typedef struct { + gchar *dn; + PurpleCertificate *crt; +} x509_ca_element; + +static void +x509_ca_element_free(x509_ca_element *el) +{ + if (NULL == el) return; + + g_free(el->dn); + purple_certificate_destroy(el->crt); + g_free(el); +} + +/** System directory to probe for CA certificates */ +/* This is set in the lazy_init function */ +static const gchar *x509_ca_syspath = NULL; + +/** A list of loaded CAs, populated from the above path whenever the lazy_init + happens. Contains pointers to x509_ca_elements */ +static GList *x509_ca_certs = NULL; + +/** Used for lazy initialization purposes. */ +static gboolean x509_ca_initialized = FALSE; + +/** Adds a certificate to the in-memory cache, doing nothing else */ +static gboolean +x509_ca_quiet_put_cert(PurpleCertificate *crt) +{ + x509_ca_element *el; + + /* lazy_init calls this function, so calling lazy_init here is a + Bad Thing */ + + g_return_val_if_fail(crt, FALSE); + g_return_val_if_fail(crt->scheme, FALSE); + /* Make sure that this is some kind of X.509 certificate */ + /* TODO: Perhaps just check crt->scheme->name instead? */ + g_return_val_if_fail(crt->scheme == purple_certificate_find_scheme(x509_ca.scheme_name), FALSE); + + el = g_new0(x509_ca_element, 1); + el->dn = purple_certificate_get_unique_id(crt); + el->crt = purple_certificate_copy(crt); + x509_ca_certs = g_list_prepend(x509_ca_certs, el); + + return TRUE; +} + +/* Since the libpurple CertificatePools get registered before plugins are + loaded, an X.509 Scheme is generally not available when x509_ca_init is + called, but x509_ca requires X.509 operations in order to properly load. + + To solve this, I present the lazy_init function. It attempts to finish + initialization of the Pool, but it usually fails when it is called from + x509_ca_init. However, this is OK; initialization is then simply deferred + until someone tries to use functions from the pool. */ +static gboolean +x509_ca_lazy_init(void) +{ + PurpleCertificateScheme *x509; + GDir *certdir; + const gchar *entry; + GPatternSpec *pempat; + + if (x509_ca_initialized) return TRUE; + + /* Check that X.509 is registered */ + x509 = purple_certificate_find_scheme(x509_ca.scheme_name); + if ( !x509 ) { + purple_debug_info("certificate/x509/ca", + "Lazy init failed because an X.509 Scheme " + "is not yet registered. Maybe it will be " + "better later.\n"); + return FALSE; + } + + /* Attempt to point at the appropriate system path */ + if (NULL == x509_ca_syspath) { +#ifdef _WIN32 + x509_ca_syspath = g_build_filename(DATADIR, + "ca-certs", NULL); +#else + x509_ca_syspath = g_build_filename(DATADIR, + "purple", "ca-certs", NULL); +#endif + } + + /* Populate the certificates pool from the system path */ + certdir = g_dir_open(x509_ca_syspath, 0, NULL); + g_return_val_if_fail(certdir, FALSE); + + /* Use a glob to only read .pem files */ + pempat = g_pattern_spec_new("*.pem"); + + while ( (entry = g_dir_read_name(certdir)) ) { + gchar *fullpath; + PurpleCertificate *crt; + + if ( !g_pattern_match_string(pempat, entry) ) { + continue; + } + + fullpath = g_build_filename(x509_ca_syspath, entry, NULL); + + /* TODO: Respond to a failure in the following? */ + crt = purple_certificate_import(x509, fullpath); + + if (x509_ca_quiet_put_cert(crt)) { + purple_debug_info("certificate/x509/ca", + "Loaded %s\n", + fullpath); + } else { + purple_debug_error("certificate/x509/ca", + "Failed to load %s\n", + fullpath); + } + + purple_certificate_destroy(crt); + g_free(fullpath); + } + + g_pattern_spec_free(pempat); + g_dir_close(certdir); + + purple_debug_info("certificate/x509/ca", + "Lazy init completed.\n"); + x509_ca_initialized = TRUE; + return TRUE; +} + +static gboolean +x509_ca_init(void) +{ + /* Attempt to initialize now, but if it doesn't work, that's OK; + it will get done later */ + if ( ! x509_ca_lazy_init()) { + purple_debug_info("certificate/x509/ca", + "Init failed, probably because a " + "dependency is not yet registered. " + "It has been deferred to later.\n"); + } + + return TRUE; +} + +static void +x509_ca_uninit(void) +{ + GList *l; + + for (l = x509_ca_certs; l; l = l->next) { + x509_ca_element *el = l->data; + x509_ca_element_free(el); + } + g_list_free(x509_ca_certs); + x509_ca_certs = NULL; + x509_ca_initialized = FALSE; +} + +/** Look up a ca_element by dn */ +static x509_ca_element * +x509_ca_locate_cert(GList *lst, const gchar *dn) +{ + GList *cur; + + for (cur = lst; cur; cur = cur->next) { + x509_ca_element *el = cur->data; + /* TODO: Unsafe? */ + if ( !strcmp(dn, el->dn) ) { + return el; + } + } + return NULL; +} + +static gboolean +x509_ca_cert_in_pool(const gchar *id) +{ + g_return_val_if_fail(x509_ca_lazy_init(), FALSE); + g_return_val_if_fail(id, FALSE); + + if (x509_ca_locate_cert(x509_ca_certs, id) != NULL) { + return TRUE; + } else { + return FALSE; + } + + return FALSE; +} + +static PurpleCertificate * +x509_ca_get_cert(const gchar *id) +{ + PurpleCertificate *crt = NULL; + x509_ca_element *el; + + g_return_val_if_fail(x509_ca_lazy_init(), NULL); + g_return_val_if_fail(id, NULL); + + /* Search the memory-cached pool */ + el = x509_ca_locate_cert(x509_ca_certs, id); + + if (el != NULL) { + /* Make a copy of the memcached one for the function caller + to play with */ + crt = purple_certificate_copy(el->crt); + } else { + crt = NULL; + } + + return crt; +} + +static gboolean +x509_ca_put_cert(const gchar *id, PurpleCertificate *crt) +{ + gboolean ret = FALSE; + + g_return_val_if_fail(x509_ca_lazy_init(), FALSE); + + /* TODO: This is a quick way of doing this. At some point the change + ought to be flushed to disk somehow. */ + ret = x509_ca_quiet_put_cert(crt); + + return ret; +} + +static gboolean +x509_ca_delete_cert(const gchar *id) +{ + x509_ca_element *el; + + g_return_val_if_fail(x509_ca_lazy_init(), FALSE); + g_return_val_if_fail(id, FALSE); + + /* Is the id even in the pool? */ + el = x509_ca_locate_cert(x509_ca_certs, id); + if ( el == NULL ) { + purple_debug_warning("certificate/x509/ca", + "Id %s wasn't in the pool\n", + id); + return FALSE; + } + + /* Unlink it from the memory cache and destroy it */ + x509_ca_certs = g_list_remove(x509_ca_certs, el); + x509_ca_element_free(el); + + return TRUE; +} + +static GList * +x509_ca_get_idlist(void) +{ + GList *l, *idlist; + + g_return_val_if_fail(x509_ca_lazy_init(), NULL); + + idlist = NULL; + for (l = x509_ca_certs; l; l = l->next) { + x509_ca_element *el = l->data; + idlist = g_list_prepend(idlist, g_strdup(el->dn)); + } + + return idlist; +} + + +static PurpleCertificatePool x509_ca = { + "x509", /* Scheme name */ + "ca", /* Pool name */ + N_("Certificate Authorities"),/* User-friendly name */ + NULL, /* Internal data */ + x509_ca_init, /* init */ + x509_ca_uninit, /* uninit */ + x509_ca_cert_in_pool, /* Certificate exists? */ + x509_ca_get_cert, /* Cert retriever */ + x509_ca_put_cert, /* Cert writer */ + x509_ca_delete_cert, /* Cert remover */ + x509_ca_get_idlist, /* idlist retriever */ + + NULL, + NULL, + NULL, + NULL + +}; + + + +/***** Cache of certificates given by TLS/SSL peers *****/ +static PurpleCertificatePool x509_tls_peers; + +static gboolean +x509_tls_peers_init(void) +{ + gchar *poolpath; + int ret; + + /* Set up key cache here if it isn't already done */ + poolpath = purple_certificate_pool_mkpath(&x509_tls_peers, NULL); + ret = purple_build_dir(poolpath, 0700); /* Make it this user only */ + + g_free(poolpath); + + g_return_val_if_fail(ret == 0, FALSE); + return TRUE; +} + +static gboolean +x509_tls_peers_cert_in_pool(const gchar *id) +{ + gchar *keypath; + gboolean ret = FALSE; + + g_return_val_if_fail(id, FALSE); + + keypath = purple_certificate_pool_mkpath(&x509_tls_peers, id); + + ret = g_file_test(keypath, G_FILE_TEST_IS_REGULAR); + + g_free(keypath); + return ret; +} + +static PurpleCertificate * +x509_tls_peers_get_cert(const gchar *id) +{ + PurpleCertificateScheme *x509; + PurpleCertificate *crt; + gchar *keypath; + + g_return_val_if_fail(id, NULL); + + /* Is it in the pool? */ + if ( !x509_tls_peers_cert_in_pool(id) ) { + return NULL; + } + + /* Look up the X.509 scheme */ + x509 = purple_certificate_find_scheme("x509"); + g_return_val_if_fail(x509, NULL); + + /* Okay, now find and load that key */ + keypath = purple_certificate_pool_mkpath(&x509_tls_peers, id); + crt = purple_certificate_import(x509, keypath); + + g_free(keypath); + + return crt; +} + +static gboolean +x509_tls_peers_put_cert(const gchar *id, PurpleCertificate *crt) +{ + gboolean ret = FALSE; + gchar *keypath; + + g_return_val_if_fail(crt, FALSE); + g_return_val_if_fail(crt->scheme, FALSE); + /* Make sure that this is some kind of X.509 certificate */ + /* TODO: Perhaps just check crt->scheme->name instead? */ + g_return_val_if_fail(crt->scheme == purple_certificate_find_scheme(x509_tls_peers.scheme_name), FALSE); + + /* Work out the filename and export */ + keypath = purple_certificate_pool_mkpath(&x509_tls_peers, id); + ret = purple_certificate_export(keypath, crt); + + g_free(keypath); + return ret; +} + +static gboolean +x509_tls_peers_delete_cert(const gchar *id) +{ + gboolean ret = FALSE; + gchar *keypath; + + g_return_val_if_fail(id, FALSE); + + /* Is the id even in the pool? */ + if (!x509_tls_peers_cert_in_pool(id)) { + purple_debug_warning("certificate/tls_peers", + "Id %s wasn't in the pool\n", + id); + return FALSE; + } + + /* OK, so work out the keypath and delete the thing */ + keypath = purple_certificate_pool_mkpath(&x509_tls_peers, id); + if ( unlink(keypath) != 0 ) { + purple_debug_error("certificate/tls_peers", + "Unlink of %s failed!\n", + keypath); + ret = FALSE; + } else { + ret = TRUE; + } + + g_free(keypath); + return ret; +} + +static GList * +x509_tls_peers_get_idlist(void) +{ + GList *idlist = NULL; + GDir *dir; + const gchar *entry; + gchar *poolpath; + + /* Get a handle on the pool directory */ + poolpath = purple_certificate_pool_mkpath(&x509_tls_peers, NULL); + dir = g_dir_open(poolpath, + 0, /* No flags */ + NULL); /* Not interested in what the error is */ + g_free(poolpath); + + g_return_val_if_fail(dir, NULL); + + /* Traverse the directory listing and create an idlist */ + while ( (entry = g_dir_read_name(dir)) != NULL ) { + /* Unescape the filename */ + const char *unescaped = purple_unescape_filename(entry); + + /* Copy the entry name into our list (GLib owns the original + string) */ + idlist = g_list_prepend(idlist, g_strdup(unescaped)); + } + + /* Release the directory */ + g_dir_close(dir); + + return idlist; +} + +static PurpleCertificatePool x509_tls_peers = { + "x509", /* Scheme name */ + "tls_peers", /* Pool name */ + N_("SSL Peers Cache"), /* User-friendly name */ + NULL, /* Internal data */ + x509_tls_peers_init, /* init */ + NULL, /* uninit not required */ + x509_tls_peers_cert_in_pool, /* Certificate exists? */ + x509_tls_peers_get_cert, /* Cert retriever */ + x509_tls_peers_put_cert, /* Cert writer */ + x509_tls_peers_delete_cert, /* Cert remover */ + x509_tls_peers_get_idlist, /* idlist retriever */ + + NULL, + NULL, + NULL, + NULL +}; + + +/***** A Verifier that uses the tls_peers cache and the CA pool to validate certificates *****/ +static PurpleCertificateVerifier x509_tls_cached; + + +/* The following is several hacks piled together and needs to be fixed. + * It exists because show_cert (see its comments) needs the original reason + * given to user_auth in order to rebuild the dialog. + */ +/* TODO: This will cause a ua_ctx to become memleaked if the request(s) get + closed by handle or otherwise abnormally. */ +typedef struct { + PurpleCertificateVerificationRequest *vrq; + gchar *reason; +} x509_tls_cached_ua_ctx; + +static x509_tls_cached_ua_ctx * +x509_tls_cached_ua_ctx_new(PurpleCertificateVerificationRequest *vrq, + const gchar *reason) +{ + x509_tls_cached_ua_ctx *c; + + c = g_new0(x509_tls_cached_ua_ctx, 1); + c->vrq = vrq; + c->reason = g_strdup(reason); + + return c; +} + + +static void +x509_tls_cached_ua_ctx_free(x509_tls_cached_ua_ctx *c) +{ + g_return_if_fail(c); + g_free(c->reason); + g_free(c); +} + +static void +x509_tls_cached_user_auth(PurpleCertificateVerificationRequest *vrq, + const gchar *reason); + +static void +x509_tls_cached_show_cert(x509_tls_cached_ua_ctx *c, gint id) +{ + PurpleCertificate *disp_crt = c->vrq->cert_chain->data; + + /* Since clicking a button closes the request, show it again */ + x509_tls_cached_user_auth(c->vrq, c->reason); + + /* Show the certificate AFTER re-opening the dialog so that this + appears above the other */ + purple_certificate_display_x509(disp_crt); + + x509_tls_cached_ua_ctx_free(c); +} + +static void +x509_tls_cached_user_auth_cb (x509_tls_cached_ua_ctx *c, gint id) +{ + PurpleCertificateVerificationRequest *vrq; + PurpleCertificatePool *tls_peers; + + g_return_if_fail(c); + g_return_if_fail(c->vrq); + + vrq = c->vrq; + + x509_tls_cached_ua_ctx_free(c); + + tls_peers = purple_certificate_find_pool("x509","tls_peers"); + + if (2 == id) { + gchar *cache_id = vrq->subject_name; + purple_debug_info("certificate/x509/tls_cached", + "User ACCEPTED cert\nCaching first in chain for future use as %s...\n", + cache_id); + + purple_certificate_pool_store(tls_peers, cache_id, + vrq->cert_chain->data); + + purple_certificate_verify_complete(vrq, + PURPLE_CERTIFICATE_VALID); + } else { + purple_debug_info("certificate/x509/tls_cached", + "User REJECTED cert\n"); + purple_certificate_verify_complete(vrq, + PURPLE_CERTIFICATE_INVALID); + } +} + +static void +x509_tls_cached_user_auth_accept_cb(x509_tls_cached_ua_ctx *c, gint ignore) +{ + x509_tls_cached_user_auth_cb(c, 2); +} + +static void +x509_tls_cached_user_auth_reject_cb(x509_tls_cached_ua_ctx *c, gint ignore) +{ + x509_tls_cached_user_auth_cb(c, 1); +} + +/** Validates a certificate by asking the user + * @param reason String to explain why the user needs to accept/refuse the + * certificate. + * @todo Needs a handle argument + */ +static void +x509_tls_cached_user_auth(PurpleCertificateVerificationRequest *vrq, + const gchar *reason) +{ + gchar *primary; + + /* Make messages */ + primary = g_strdup_printf(_("Accept certificate for %s?"), + vrq->subject_name); + + /* Make a semi-pretty display */ + purple_request_action( + vrq->cb_data, /* TODO: Find what the handle ought to be */ + _("SSL Certificate Verification"), + primary, + reason, + 2, /* Accept by default */ + NULL, /* No account */ + NULL, /* No other user */ + NULL, /* No associated conversation */ + x509_tls_cached_ua_ctx_new(vrq, reason), + 3, /* Number of actions */ + _("Accept"), x509_tls_cached_user_auth_accept_cb, + _("Reject"), x509_tls_cached_user_auth_reject_cb, + _("_View Certificate..."), x509_tls_cached_show_cert); + + /* Cleanup */ + g_free(primary); +} + +static void +x509_tls_cached_peer_cert_changed(PurpleCertificateVerificationRequest *vrq) +{ + /* TODO: Prompt the user, etc. */ + + purple_debug_info("certificate/x509/tls_cached", + "Certificate for %s does not match cached. " + "Auto-rejecting!\n", + vrq->subject_name); + + purple_certificate_verify_complete(vrq, PURPLE_CERTIFICATE_INVALID); + return; +} + +static void +x509_tls_cached_cert_in_cache(PurpleCertificateVerificationRequest *vrq) +{ + /* TODO: Looking this up by name over and over is expensive. + Fix, please! */ + PurpleCertificatePool *tls_peers = + purple_certificate_find_pool(x509_tls_cached.scheme_name, + "tls_peers"); + + /* The peer's certificate should be the first in the list */ + PurpleCertificate *peer_crt = + (PurpleCertificate *) vrq->cert_chain->data; + + PurpleCertificate *cached_crt; + GByteArray *peer_fpr, *cached_fpr; + + /* Load up the cached certificate */ + cached_crt = purple_certificate_pool_retrieve( + tls_peers, vrq->subject_name); + if ( !cached_crt ) { + purple_debug_error("certificate/x509/tls_cached", + "Lookup failed on cached certificate!\n" + "It was here just a second ago. Forwarding " + "to cert_changed.\n"); + /* vrq now becomes the problem of cert_changed */ + x509_tls_cached_peer_cert_changed(vrq); + } + + /* Now get SHA1 sums for both and compare them */ + /* TODO: This is not an elegant way to compare certs */ + peer_fpr = purple_certificate_get_fingerprint_sha1(peer_crt); + cached_fpr = purple_certificate_get_fingerprint_sha1(cached_crt); + if (!memcmp(peer_fpr->data, cached_fpr->data, peer_fpr->len)) { + purple_debug_info("certificate/x509/tls_cached", + "Peer cert matched cached\n"); + /* vrq is now finished */ + purple_certificate_verify_complete(vrq, + PURPLE_CERTIFICATE_VALID); + } else { + purple_debug_info("certificate/x509/tls_cached", + "Peer cert did NOT match cached\n"); + /* vrq now becomes the problem of cert_changed */ + x509_tls_cached_peer_cert_changed(vrq); + } + + purple_certificate_destroy(cached_crt); + g_byte_array_free(peer_fpr, TRUE); + g_byte_array_free(cached_fpr, TRUE); +} + +/* For when we've never communicated with this party before */ +/* TODO: Need ways to specify possibly multiple problems with a cert, or at + least reprioritize them. For example, maybe the signature ought to be + checked BEFORE the hostname checking? */ +static void +x509_tls_cached_unknown_peer(PurpleCertificateVerificationRequest *vrq) +{ + PurpleCertificatePool *ca, *tls_peers; + PurpleCertificate *end_crt, *ca_crt, *peer_crt; + GList *chain = vrq->cert_chain; + GList *last; + gchar *ca_id; + + peer_crt = (PurpleCertificate *) chain->data; + + /* First, check that the hostname matches */ + if ( ! purple_certificate_check_subject_name(peer_crt, + vrq->subject_name) ) { + gchar *sn = purple_certificate_get_subject_name(peer_crt); + gchar *msg; + + purple_debug_info("certificate/x509/tls_cached", + "Name mismatch: Certificate given for %s " + "has a name of %s\n", + vrq->subject_name, sn); + + /* Prompt the user to authenticate the certificate */ + /* TODO: Provide the user with more guidance about why he is + being prompted */ + /* vrq will be completed by user_auth */ + msg = g_strdup_printf(_("The certificate presented by \"%s\" " + "claims to be from \"%s\" instead. " + "This could mean that you are not " + "connecting to the service you " + "believe you are."), + vrq->subject_name, sn); + + x509_tls_cached_user_auth(vrq,msg); + + g_free(sn); + g_free(msg); + return; + } /* if (name mismatch) */ + + /* TODO: Figure out a way to check for a bad signature, as opposed to + "not self-signed" */ + if ( purple_certificate_signed_by(peer_crt, peer_crt) ) { + gchar *msg; + + purple_debug_info("certificate/x509/tls_cached", + "Certificate for %s is self-signed.\n", + vrq->subject_name); + + /* Prompt the user to authenticate the certificate */ + /* vrq will be completed by user_auth */ + msg = g_strdup_printf(_("The certificate presented by \"%s\" " + "is self-signed. It cannot be " + "automatically checked."), + vrq->subject_name); + + x509_tls_cached_user_auth(vrq,msg); + + g_free(msg); + return; + } /* if (name mismatch) */ + + /* Next, check that the certificate chain is valid */ + if ( ! purple_certificate_check_signature_chain(chain) ) { + /* TODO: Tell the user where the chain broke? */ + /* TODO: This error will hopelessly confuse any + non-elite user. */ + gchar *secondary; + + secondary = g_strdup_printf(_("The certificate chain presented" + " for %s is not valid."), + vrq->subject_name); + + /* TODO: Make this error either block the ensuing SSL + connection error until the user dismisses this one, or + stifle it. */ + purple_notify_error(NULL, /* TODO: Probably wrong. */ + _("SSL Certificate Error"), + _("Invalid certificate chain"), + secondary ); + g_free(secondary); + + /* Okay, we're done here */ + purple_certificate_verify_complete(vrq, + PURPLE_CERTIFICATE_INVALID); + } /* if (signature chain not good) */ + + /* Next, attempt to verify the last certificate against a CA */ + ca = purple_certificate_find_pool(x509_tls_cached.scheme_name, "ca"); + + /* If, for whatever reason, there is no Certificate Authority pool + loaded, we will simply present it to the user for checking. */ + if ( !ca ) { + purple_debug_error("certificate/x509/tls_cached", + "No X.509 Certificate Authority pool " + "could be found!\n"); + + /* vrq will be completed by user_auth */ + x509_tls_cached_user_auth(vrq,_("You have no database of root " + "certificates, so this " + "certificate cannot be " + "validated.")); + return; + } + + last = g_list_last(chain); + end_crt = (PurpleCertificate *) last->data; + + /* Attempt to look up the last certificate's issuer */ + ca_id = purple_certificate_get_issuer_unique_id(end_crt); + purple_debug_info("certificate/x509/tls_cached", + "Checking for a CA with DN=%s\n", + ca_id); + if ( !purple_certificate_pool_contains(ca, ca_id) ) { + purple_debug_info("certificate/x509/tls_cached", + "Certificate Authority with DN='%s' not " + "found. I'll prompt the user, I guess.\n", + ca_id); + g_free(ca_id); + /* vrq will be completed by user_auth */ + x509_tls_cached_user_auth(vrq,_("The root certificate this " + "one claims to be issued by " + "is unknown to Pidgin.")); + return; + } + + ca_crt = purple_certificate_pool_retrieve(ca, ca_id); + g_free(ca_id); + if (!ca_crt) { + purple_debug_error("certificate/x509/tls_cached", + "Certificate authority disappeared out " + "underneath me!\n"); + purple_certificate_verify_complete(vrq, + PURPLE_CERTIFICATE_INVALID); + return; + } + + /* Check the signature */ + if ( !purple_certificate_signed_by(end_crt, ca_crt) ) { + /* TODO: If signed_by ever returns a reason, maybe mention + that, too. */ + /* TODO: Also mention the CA involved. While I could do this + now, a full DN is a little much with which to assault the + user's poor, leaky eyes. */ + /* TODO: This error message makes my eyes cross, and I wrote it */ + gchar * secondary = + g_strdup_printf(_("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."), + vrq->subject_name); + + purple_notify_error(NULL, /* TODO: Probably wrong */ + _("SSL Certificate Error"), + _("Invalid certificate authority" + " signature"), + secondary); + g_free(secondary); + + /* Signal "bad cert" */ + purple_certificate_verify_complete(vrq, + PURPLE_CERTIFICATE_INVALID); + return; + } /* if (CA signature not good) */ + + /* If we reach this point, the certificate is good. */ + /* Look up the local cache and store it there for future use */ + tls_peers = purple_certificate_find_pool(x509_tls_cached.scheme_name, + "tls_peers"); + + if (tls_peers) { + if (!purple_certificate_pool_store(tls_peers,vrq->subject_name, + peer_crt) ) { + purple_debug_error("certificate/x509/tls_cached", + "FAILED to cache peer certificate\n"); + } + } else { + purple_debug_error("certificate/x509/tls_cached", + "Unable to locate tls_peers certificate " + "cache.\n"); + } + + /* Whew! Done! */ + purple_certificate_verify_complete(vrq, PURPLE_CERTIFICATE_VALID); +} + +static void +x509_tls_cached_start_verify(PurpleCertificateVerificationRequest *vrq) +{ + const gchar *tls_peers_name = "tls_peers"; /* Name of local cache */ + PurpleCertificatePool *tls_peers; + + g_return_if_fail(vrq); + + purple_debug_info("certificate/x509/tls_cached", + "Starting verify for %s\n", + vrq->subject_name); + + tls_peers = purple_certificate_find_pool(x509_tls_cached.scheme_name,tls_peers_name); + + /* TODO: This should probably just prompt the user instead of throwing + an angry fit */ + if (!tls_peers) { + purple_debug_error("certificate/x509/tls_cached", + "Couldn't find local peers cache %s\nReturning INVALID to callback\n", + tls_peers_name); + + purple_certificate_verify_complete(vrq, + PURPLE_CERTIFICATE_INVALID); + return; + } + + /* Check if the peer has a certificate cached already */ + purple_debug_info("certificate/x509/tls_cached", + "Checking for cached cert...\n"); + if (purple_certificate_pool_contains(tls_peers, vrq->subject_name)) { + purple_debug_info("certificate/x509/tls_cached", + "...Found cached cert\n"); + /* vrq is now the responsibility of cert_in_cache */ + x509_tls_cached_cert_in_cache(vrq); + } else { + purple_debug_info("certificate/x509/tls_cached", + "...Not in cache\n"); + /* vrq now becomes the problem of unknown_peer */ + x509_tls_cached_unknown_peer(vrq); + } +} + +static void +x509_tls_cached_destroy_request(PurpleCertificateVerificationRequest *vrq) +{ + g_return_if_fail(vrq); +} + +static PurpleCertificateVerifier x509_tls_cached = { + "x509", /* Scheme name */ + "tls_cached", /* Verifier name */ + x509_tls_cached_start_verify, /* Verification begin */ + x509_tls_cached_destroy_request,/* Request cleanup */ + + NULL, + NULL, + NULL, + NULL + +}; + +/****************************************************************************/ +/* Subsystem */ +/****************************************************************************/ +void +purple_certificate_init(void) +{ + /* Register builtins */ + purple_certificate_register_verifier(&x509_singleuse); + purple_certificate_register_pool(&x509_ca); + purple_certificate_register_pool(&x509_tls_peers); + purple_certificate_register_verifier(&x509_tls_cached); +} + +void +purple_certificate_uninit(void) +{ + GList *full_list, *l; + + /* Unregister all Schemes */ + full_list = g_list_copy(cert_schemes); /* Make a working copy */ + for (l = full_list; l; l = l->next) { + purple_certificate_unregister_scheme( + (PurpleCertificateScheme *) l->data ); + } + g_list_free(full_list); + + /* Unregister all Verifiers */ + full_list = g_list_copy(cert_verifiers); /* Make a working copy */ + for (l = full_list; l; l = l->next) { + purple_certificate_unregister_verifier( + (PurpleCertificateVerifier *) l->data ); + } + g_list_free(full_list); + + /* Unregister all Pools */ + full_list = g_list_copy(cert_pools); /* Make a working copy */ + for (l = full_list; l; l = l->next) { + purple_certificate_unregister_pool( + (PurpleCertificatePool *) l->data ); + } + g_list_free(full_list); +} + +gpointer +purple_certificate_get_handle(void) +{ + static gint handle; + return &handle; +} + +PurpleCertificateScheme * +purple_certificate_find_scheme(const gchar *name) +{ + PurpleCertificateScheme *scheme = NULL; + GList *l; + + g_return_val_if_fail(name, NULL); + + /* Traverse the list of registered schemes and locate the + one whose name matches */ + for(l = cert_schemes; l; l = l->next) { + scheme = (PurpleCertificateScheme *)(l->data); + + /* Name matches? that's our man */ + if(!g_ascii_strcasecmp(scheme->name, name)) + return scheme; + } + + purple_debug_warning("certificate", + "CertificateScheme %s requested but not found.\n", + name); + + /* TODO: Signalling and such? */ + + return NULL; +} + +GList * +purple_certificate_get_schemes(void) +{ + return cert_schemes; +} + +gboolean +purple_certificate_register_scheme(PurpleCertificateScheme *scheme) +{ + g_return_val_if_fail(scheme != NULL, FALSE); + + /* Make sure no scheme is registered with the same name */ + if (purple_certificate_find_scheme(scheme->name) != NULL) { + return FALSE; + } + + /* Okay, we're golden. Register it. */ + cert_schemes = g_list_prepend(cert_schemes, scheme); + + /* TODO: Signalling and such? */ + + purple_debug_info("certificate", + "CertificateScheme %s registered\n", + scheme->name); + + return TRUE; +} + +gboolean +purple_certificate_unregister_scheme(PurpleCertificateScheme *scheme) +{ + if (NULL == scheme) { + purple_debug_warning("certificate", + "Attempting to unregister NULL scheme\n"); + return FALSE; + } + + /* TODO: signalling? */ + + /* TODO: unregister all CertificateVerifiers for this scheme?*/ + /* TODO: unregister all CertificatePools for this scheme? */ + /* Neither of the above should be necessary, though */ + cert_schemes = g_list_remove(cert_schemes, scheme); + + purple_debug_info("certificate", + "CertificateScheme %s unregistered\n", + scheme->name); + + + return TRUE; +} + +PurpleCertificateVerifier * +purple_certificate_find_verifier(const gchar *scheme_name, const gchar *ver_name) +{ + PurpleCertificateVerifier *vr = NULL; + GList *l; + + g_return_val_if_fail(scheme_name, NULL); + g_return_val_if_fail(ver_name, NULL); + + /* Traverse the list of registered verifiers and locate the + one whose name matches */ + for(l = cert_verifiers; l; l = l->next) { + vr = (PurpleCertificateVerifier *)(l->data); + + /* Scheme and name match? */ + if(!g_ascii_strcasecmp(vr->scheme_name, scheme_name) && + !g_ascii_strcasecmp(vr->name, ver_name)) + return vr; + } + + purple_debug_warning("certificate", + "CertificateVerifier %s, %s requested but not found.\n", + scheme_name, ver_name); + + /* TODO: Signalling and such? */ + + return NULL; +} + + +GList * +purple_certificate_get_verifiers(void) +{ + return cert_verifiers; +} + +gboolean +purple_certificate_register_verifier(PurpleCertificateVerifier *vr) +{ + g_return_val_if_fail(vr != NULL, FALSE); + + /* Make sure no verifier is registered with the same scheme/name */ + if (purple_certificate_find_verifier(vr->scheme_name, vr->name) != NULL) { + return FALSE; + } + + /* Okay, we're golden. Register it. */ + cert_verifiers = g_list_prepend(cert_verifiers, vr); + + /* TODO: Signalling and such? */ + + purple_debug_info("certificate", + "CertificateVerifier %s registered\n", + vr->name); + return TRUE; +} + +gboolean +purple_certificate_unregister_verifier(PurpleCertificateVerifier *vr) +{ + if (NULL == vr) { + purple_debug_warning("certificate", + "Attempting to unregister NULL verifier\n"); + return FALSE; + } + + /* TODO: signalling? */ + + cert_verifiers = g_list_remove(cert_verifiers, vr); + + + purple_debug_info("certificate", + "CertificateVerifier %s unregistered\n", + vr->name); + + return TRUE; +} + +PurpleCertificatePool * +purple_certificate_find_pool(const gchar *scheme_name, const gchar *pool_name) +{ + PurpleCertificatePool *pool = NULL; + GList *l; + + g_return_val_if_fail(scheme_name, NULL); + g_return_val_if_fail(pool_name, NULL); + + /* Traverse the list of registered pools and locate the + one whose name matches */ + for(l = cert_pools; l; l = l->next) { + pool = (PurpleCertificatePool *)(l->data); + + /* Scheme and name match? */ + if(!g_ascii_strcasecmp(pool->scheme_name, scheme_name) && + !g_ascii_strcasecmp(pool->name, pool_name)) + return pool; + } + + purple_debug_warning("certificate", + "CertificatePool %s, %s requested but not found.\n", + scheme_name, pool_name); + + /* TODO: Signalling and such? */ + + return NULL; + +} + +GList * +purple_certificate_get_pools(void) +{ + return cert_pools; +} + +gboolean +purple_certificate_register_pool(PurpleCertificatePool *pool) +{ + gboolean success = FALSE; + g_return_val_if_fail(pool, FALSE); + g_return_val_if_fail(pool->scheme_name, FALSE); + g_return_val_if_fail(pool->name, FALSE); + g_return_val_if_fail(pool->fullname, FALSE); + + /* Make sure no pools are registered under this name */ + if (purple_certificate_find_pool(pool->scheme_name, pool->name)) { + return FALSE; + } + + /* Initialize the pool if needed */ + if (pool->init) { + success = pool->init(); + } else { + success = TRUE; + } + + if (success) { + /* Register the Pool */ + cert_pools = g_list_prepend(cert_pools, pool); + + /* TODO: Emit a signal that the pool got registered */ + + PURPLE_DBUS_REGISTER_POINTER(pool, PurpleCertificatePool); + purple_signal_register(pool, /* Signals emitted from pool */ + "certificate-stored", + purple_marshal_VOID__POINTER_POINTER, + NULL, /* No callback return value */ + 2, /* Two non-data arguments */ + purple_value_new(PURPLE_TYPE_SUBTYPE, + PURPLE_SUBTYPE_CERTIFICATEPOOL), + purple_value_new(PURPLE_TYPE_STRING)); + + purple_signal_register(pool, /* Signals emitted from pool */ + "certificate-deleted", + purple_marshal_VOID__POINTER_POINTER, + NULL, /* No callback return value */ + 2, /* Two non-data arguments */ + purple_value_new(PURPLE_TYPE_SUBTYPE, + PURPLE_SUBTYPE_CERTIFICATEPOOL), + purple_value_new(PURPLE_TYPE_STRING)); + + + purple_debug_info("certificate", + "CertificatePool %s registered\n", + pool->name); + return TRUE; + } else { + return FALSE; + } + + /* Control does not reach this point */ +} + +gboolean +purple_certificate_unregister_pool(PurpleCertificatePool *pool) +{ + if (NULL == pool) { + purple_debug_warning("certificate", + "Attempting to unregister NULL pool\n"); + return FALSE; + } + + /* Check that the pool is registered */ + if (!g_list_find(cert_pools, pool)) { + purple_debug_warning("certificate", + "Pool to unregister isn't registered!\n"); + + return FALSE; + } + + /* Uninit the pool if needed */ + PURPLE_DBUS_UNREGISTER_POINTER(pool); + if (pool->uninit) { + pool->uninit(); + } + + cert_pools = g_list_remove(cert_pools, pool); + + /* TODO: Signalling? */ + purple_signal_unregister(pool, "certificate-stored"); + purple_signal_unregister(pool, "certificate-deleted"); + + purple_debug_info("certificate", + "CertificatePool %s unregistered\n", + pool->name); + return TRUE; +} + +/****************************************************************************/ +/* Scheme-specific functions */ +/****************************************************************************/ + +void +purple_certificate_display_x509(PurpleCertificate *crt) +{ + gchar *sha_asc; + GByteArray *sha_bin; + gchar *cn; + time_t activation, expiration; + gchar *activ_str, *expir_str; + gchar *secondary; + + /* Pull out the SHA1 checksum */ + sha_bin = purple_certificate_get_fingerprint_sha1(crt); + /* Now decode it for display */ + sha_asc = purple_base16_encode_chunked(sha_bin->data, + sha_bin->len); + + /* Get the cert Common Name */ + /* TODO: Will break on CA certs */ + cn = purple_certificate_get_subject_name(crt); + + /* Get the certificate times */ + /* TODO: Check the times against localtime */ + /* TODO: errorcheck? */ + if (!purple_certificate_get_times(crt, &activation, &expiration)) { + purple_debug_error("certificate", + "Failed to get certificate times!\n"); + activation = expiration = 0; + } + activ_str = g_strdup(ctime(&activation)); + expir_str = g_strdup(ctime(&expiration)); + + /* Make messages */ + secondary = g_strdup_printf(_("Common name: %s\n\n" + "Fingerprint (SHA1): %s\n\n" + "Activation date: %s\n" + "Expiration date: %s\n"), + cn, sha_asc, activ_str, expir_str); + + /* Make a semi-pretty display */ + purple_notify_info( + NULL, /* TODO: Find what the handle ought to be */ + _("Certificate Information"), + "", + secondary); + + /* Cleanup */ + g_free(cn); + g_free(secondary); + g_free(sha_asc); + g_free(activ_str); + g_free(expir_str); + g_byte_array_free(sha_bin, TRUE); +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/certificate.h Wed Sep 12 19:11:38 2007 +0000 @@ -0,0 +1,792 @@ +/** + * @file certificate.h Public-Key Certificate API + * @ingroup core + */ + +/* + * + * purple + * + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * 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 _PURPLE_CERTIFICATE_H +#define _PURPLE_CERTIFICATE_H + +#include <glib.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +typedef enum +{ + PURPLE_CERTIFICATE_INVALID = 0, + PURPLE_CERTIFICATE_VALID = 1 +} PurpleCertificateVerificationStatus; + +typedef struct _PurpleCertificate PurpleCertificate; +typedef struct _PurpleCertificatePool PurpleCertificatePool; +typedef struct _PurpleCertificateScheme PurpleCertificateScheme; +typedef struct _PurpleCertificateVerifier PurpleCertificateVerifier; +typedef struct _PurpleCertificateVerificationRequest PurpleCertificateVerificationRequest; + +/** + * Callback function for the results of a verification check + * @param st Status code + * @param userdata User-defined data + */ +typedef void (*PurpleCertificateVerifiedCallback) + (PurpleCertificateVerificationStatus st, + gpointer userdata); + +/** A certificate instance + * + * An opaque data structure representing a single certificate under some + * CertificateScheme + */ +struct _PurpleCertificate +{ + /** Scheme this certificate is under */ + PurpleCertificateScheme * scheme; + /** Opaque pointer to internal data */ + gpointer data; +}; + +/** + * Database for retrieval or storage of Certificates + * + * More or less a hash table; all lookups and writes are controlled by a string + * key. + */ +struct _PurpleCertificatePool +{ + /** Scheme this Pool operates for */ + gchar *scheme_name; + /** Internal name to refer to the pool by */ + gchar *name; + + /** User-friendly name for this type + * ex: N_("SSL Servers") + * When this is displayed anywhere, it should be i18ned + * ex: _(pool->fullname) + */ + gchar *fullname; + + /** Internal pool data */ + gpointer data; + + /** + * Set up the Pool's internal state + * + * Upon calling purple_certificate_register_pool() , this function will + * be called. May be NULL. + * @return TRUE if the initialization succeeded, otherwise FALSE + */ + gboolean (* init)(void); + + /** + * Uninit the Pool's internal state + * + * Will be called by purple_certificate_unregister_pool() . May be NULL + */ + void (* uninit)(void); + + /** Check for presence of a certificate in the pool using unique ID */ + gboolean (* cert_in_pool)(const gchar *id); + /** Retrieve a PurpleCertificate from the pool */ + PurpleCertificate * (* get_cert)(const gchar *id); + /** Add a certificate to the pool. Must overwrite any other + * certificates sharing the same ID in the pool. + * @return TRUE if the operation succeeded, otherwise FALSE + */ + gboolean (* put_cert)(const gchar *id, PurpleCertificate *crt); + /** Delete a certificate from the pool */ + gboolean (* delete_cert)(const gchar *id); + + /** Returns a list of IDs stored in the pool */ + GList * (* get_idlist)(void); + + void (*_purple_reserved1)(void); + void (*_purple_reserved2)(void); + void (*_purple_reserved3)(void); + void (*_purple_reserved4)(void); +}; + +/** A certificate type + * + * A CertificateScheme must implement all of the fields in the structure, + * and register it using purple_certificate_register_scheme() + * + * There may be only ONE CertificateScheme provided for each certificate + * type, as specified by the "name" field. + */ +struct _PurpleCertificateScheme +{ + /** Name of the certificate type + * ex: "x509", "pgp", etc. + * This must be globally unique - you may not register more than one + * CertificateScheme of the same name at a time. + */ + gchar * name; + + /** User-friendly name for this type + * ex: N_("X.509 Certificates") + * When this is displayed anywhere, it should be i18ned + * ex: _(scheme->fullname) + */ + gchar * fullname; + + /** Imports a certificate from a file + * + * @param filename File to import the certificate from + * @return Pointer to the newly allocated Certificate struct + * or NULL on failure. + */ + PurpleCertificate * (* import_certificate)(const gchar * filename); + + /** + * Exports a certificate to a file + * + * @param filename File to export the certificate to + * @param crt Certificate to export + * @return TRUE if the export succeeded, otherwise FALSE + * @see purple_certificate_export() + */ + gboolean (* export_certificate)(const gchar *filename, PurpleCertificate *crt); + + /** + * Duplicates a certificate + * + * Certificates are generally assumed to be read-only, so feel free to + * do any sort of reference-counting magic you want here. If this ever + * changes, please remember to change the magic accordingly. + * @return Reference to the new copy + */ + PurpleCertificate * (* copy_certificate)(PurpleCertificate *crt); + + /** Destroys and frees a Certificate structure + * + * Destroys a Certificate's internal data structures and calls + * free(crt) + * + * @param crt Certificate instance to be destroyed. It WILL NOT be + * destroyed if it is not of the correct + * CertificateScheme. Can be NULL + */ + void (* destroy_certificate)(PurpleCertificate * crt); + + /** Find whether "crt" has a valid signature from issuer "issuer" + * @see purple_certificate_signed_by() */ + gboolean (*signed_by)(PurpleCertificate *crt, PurpleCertificate *issuer); + /** + * Retrieves the certificate public key fingerprint using SHA1 + * + * @param crt Certificate instance + * @return Binary representation of SHA1 hash - must be freed using + * g_byte_array_free() + */ + GByteArray * (* get_fingerprint_sha1)(PurpleCertificate *crt); + + /** + * Retrieves a unique certificate identifier + * + * @param crt Certificate instance + * @return Newly allocated string that can be used to uniquely + * identify the certificate. + */ + gchar * (* get_unique_id)(PurpleCertificate *crt); + + /** + * Retrieves a unique identifier for the certificate's issuer + * + * @param crt Certificate instance + * @return Newly allocated string that can be used to uniquely + * identify the issuer's certificate. + */ + gchar * (* get_issuer_unique_id)(PurpleCertificate *crt); + + /** + * Gets the certificate subject's name + * + * For X.509, this is the "Common Name" field, as we're only using it + * for hostname verification at the moment + * + * @see purple_certificate_get_subject_name() + * + * @param crt Certificate instance + * @return Newly allocated string with the certificate subject. + */ + gchar * (* get_subject_name)(PurpleCertificate *crt); + + /** + * Check the subject name against that on the certificate + * @see purple_certificate_check_subject_name() + * @return TRUE if it is a match, else FALSE + */ + gboolean (* check_subject_name)(PurpleCertificate *crt, const gchar *name); + + /** Retrieve the certificate activation/expiration times */ + gboolean (* get_times)(PurpleCertificate *crt, time_t *activation, time_t *expiration); + + void (*_purple_reserved1)(void); + void (*_purple_reserved2)(void); + void (*_purple_reserved3)(void); + void (*_purple_reserved4)(void); +}; + +/** A set of operations used to provide logic for verifying a Certificate's + * authenticity. + * + * A Verifier provider must fill out these fields, then register it using + * purple_certificate_register_verifier() + * + * The (scheme_name, name) value must be unique for each Verifier - you may not + * register more than one Verifier of the same name for each Scheme + */ +struct _PurpleCertificateVerifier +{ + /** Name of the scheme this Verifier operates on + * + * The scheme will be looked up by name when a Request is generated + * using this Verifier + */ + gchar *scheme_name; + + /** Name of the Verifier - case insensitive */ + gchar *name; + + /** + * Start the verification process + * + * To be called from purple_certificate_verify once it has + * constructed the request. This will use the information in the + * given VerificationRequest to check the certificate and callback + * the requester with the verification results. + * + * @param vrq Request to process + */ + void (* start_verification)(PurpleCertificateVerificationRequest *vrq); + + /** + * Destroy a completed Request under this Verifier + * The function pointed to here is only responsible for cleaning up + * whatever PurpleCertificateVerificationRequest::data points to. + * It should not call free(vrq) + * + * @param vrq Request to destroy + */ + void (* destroy_request)(PurpleCertificateVerificationRequest *vrq); + + void (*_purple_reserved1)(void); + void (*_purple_reserved2)(void); + void (*_purple_reserved3)(void); + void (*_purple_reserved4)(void); +}; + +/** Structure for a single certificate request + * + * Useful for keeping track of the state of a verification that involves + * several steps + */ +struct _PurpleCertificateVerificationRequest +{ + /** Reference to the verification logic used */ + PurpleCertificateVerifier *verifier; + /** Reference to the scheme used. + * + * This is looked up from the Verifier when the Request is generated + */ + PurpleCertificateScheme *scheme; + + /** + * Name to check that the certificate is issued to + * + * For X.509 certificates, this is the Common Name + */ + gchar *subject_name; + + /** List of certificates in the chain to be verified (such as that returned by purple_ssl_get_peer_certificates ) + * + * This is most relevant for X.509 certificates used in SSL sessions. + * The list order should be: certificate, issuer, issuer's issuer, etc. + */ + GList *cert_chain; + + /** Internal data used by the Verifier code */ + gpointer data; + + /** Function to call with the verification result */ + PurpleCertificateVerifiedCallback cb; + /** Data to pass to the post-verification callback */ + gpointer cb_data; +}; + +/*****************************************************************************/ +/** @name Certificate Verification Functions */ +/*****************************************************************************/ +/*@{*/ + +/** + * Constructs a verification request and passed control to the specified Verifier + * + * It is possible that the callback will be called immediately upon calling + * this function. Plan accordingly. + * + * @param verifier Verification logic to use. + * @see purple_certificate_find_verifier() + * + * @param subject_name Name that should match the first certificate in the + * chain for the certificate to be valid. Will be strdup'd + * into the Request struct + * + * @param cert_chain Certificate chain to check. If there is more than one + * certificate in the chain (X.509), the peer's + * certificate comes first, then the issuer/signer's + * certificate, etc. The whole list is duplicated into the + * Request struct. + * + * @param cb Callback function to be called with whether the + * certificate was approved or not. + * @param cb_data User-defined data for the above. + */ +void +purple_certificate_verify (PurpleCertificateVerifier *verifier, + const gchar *subject_name, GList *cert_chain, + PurpleCertificateVerifiedCallback cb, + gpointer cb_data); + +/** + * Completes and destroys a VerificationRequest + * + * @param vrq Request to conclude + * @param st Success/failure code to pass to the request's + * completion callback. + */ +void +purple_certificate_verify_complete(PurpleCertificateVerificationRequest *vrq, + PurpleCertificateVerificationStatus st); + +/*@}*/ + +/*****************************************************************************/ +/** @name Certificate Functions */ +/*****************************************************************************/ +/*@{*/ + +/** + * Makes a duplicate of a certificate + * + * @param crt Instance to duplicate + * @return Pointer to new instance + */ +PurpleCertificate * +purple_certificate_copy(PurpleCertificate *crt); + +/** + * Duplicates an entire list of certificates + * + * @param crt_list List to duplicate + * @return New list copy + */ +GList * +purple_certificate_copy_list(GList *crt_list); + +/** + * Destroys and free()'s a Certificate + * + * @param crt Instance to destroy. May be NULL. + */ +void +purple_certificate_destroy (PurpleCertificate *crt); + +/** + * Destroy an entire list of Certificate instances and the containing list + * + * @param crt_list List of certificates to destroy. May be NULL. + */ +void +purple_certificate_destroy_list (GList * crt_list); + +/** + * Check whether 'crt' has a valid signature made by 'issuer' + * + * @param crt Certificate instance to check signature of + * @param issuer Certificate thought to have signed 'crt' + * + * @return TRUE if 'crt' has a valid signature made by 'issuer', + * otherwise FALSE + * @TODO Find a way to give the reason (bad signature, not the issuer, etc.) + */ +gboolean +purple_certificate_signed_by(PurpleCertificate *crt, PurpleCertificate *issuer); + +/** + * Check that a certificate chain is valid + * + * Uses purple_certificate_signed_by() to verify that each PurpleCertificate + * in the chain carries a valid signature from the next. A single-certificate + * chain is considered to be valid. + * + * @param chain List of PurpleCertificate instances comprising the chain, + * in the order certificate, issuer, issuer's issuer, etc. + * @return TRUE if the chain is valid. See description. + * @TODO Specify which certificate in the chain caused a failure + */ +gboolean +purple_certificate_check_signature_chain(GList *chain); + +/** + * Imports a PurpleCertificate from a file + * + * @param scheme Scheme to import under + * @param filename File path to import from + * @return Pointer to a new PurpleCertificate, or NULL on failure + */ +PurpleCertificate * +purple_certificate_import(PurpleCertificateScheme *scheme, const gchar *filename); + +/** + * Exports a PurpleCertificate to a file + * + * @param filename File to export the certificate to + * @param crt Certificate to export + * @return TRUE if the export succeeded, otherwise FALSE + */ +gboolean +purple_certificate_export(const gchar *filename, PurpleCertificate *crt); + + +/** + * Retrieves the certificate public key fingerprint using SHA1. + * + * @param crt Certificate instance + * @return Binary representation of the hash. You are responsible for free()ing + * this. + * @see purple_base16_encode_chunked() + */ +GByteArray * +purple_certificate_get_fingerprint_sha1(PurpleCertificate *crt); + +/** + * Get a unique identifier for the certificate + * + * @param crt Certificate instance + * @return String representing the certificate uniquely. Must be g_free()'ed + */ +gchar * +purple_certificate_get_unique_id(PurpleCertificate *crt); + +/** + * Get a unique identifier for the certificate's issuer + * + * @param crt Certificate instance + * @return String representing the certificate's issuer uniquely. Must be + * g_free()'ed + */ +gchar * +purple_certificate_get_issuer_unique_id(PurpleCertificate *crt); + +/** + * Gets the certificate subject's name + * + * For X.509, this is the "Common Name" field, as we're only using it + * for hostname verification at the moment + * + * @param crt Certificate instance + * @return Newly allocated string with the certificate subject. + */ +gchar * +purple_certificate_get_subject_name(PurpleCertificate *crt); + +/** + * Check the subject name against that on the certificate + * @param crt Certificate instance + * @param name Name to check. + * @return TRUE if it is a match, else FALSE + */ +gboolean +purple_certificate_check_subject_name(PurpleCertificate *crt, const gchar *name); + +/** + * Get the expiration/activation times. + * + * @param crt Certificate instance + * @param activation Reference to store the activation time at. May be NULL + * if you don't actually want it. + * @param expiration Reference to store the expiration time at. May be NULL + * if you don't actually want it. + * @return TRUE if the requested values were obtained, otherwise FALSE. + */ +gboolean +purple_certificate_get_times(PurpleCertificate *crt, time_t *activation, time_t *expiration); + +/*@}*/ + +/*****************************************************************************/ +/** @name Certificate Pool Functions */ +/*****************************************************************************/ +/*@{*/ +/** + * Helper function for generating file paths in ~/.purple/certificates for + * CertificatePools that use them. + * + * All components will be escaped for filesystem friendliness. + * + * @param pool CertificatePool to build a path for + * @param id Key to look up a Certificate by. May be NULL. + * @return A newly allocated path of the form + * ~/.purple/certificates/scheme_name/pool_name/unique_id + */ +gchar * +purple_certificate_pool_mkpath(PurpleCertificatePool *pool, const gchar *id); + +/** + * Determines whether a pool can be used. + * + * Checks whether the associated CertificateScheme is loaded. + * + * @param pool Pool to check + * + * @return TRUE if the pool can be used, otherwise FALSE + */ +gboolean +purple_certificate_pool_usable(PurpleCertificatePool *pool); + +/** + * Looks up the scheme the pool operates under + * + * @param pool Pool to get the scheme of + * + * @return Pointer to the pool's scheme, or NULL if it isn't loaded. + * @see purple_certificate_pool_usable() + */ +PurpleCertificateScheme * +purple_certificate_pool_get_scheme(PurpleCertificatePool *pool); + +/** + * Check for presence of an ID in a pool. + * @param pool Pool to look in + * @param id ID to look for + * @return TRUE if the ID is in the pool, else FALSE + */ +gboolean +purple_certificate_pool_contains(PurpleCertificatePool *pool, const gchar *id); + +/** + * Retrieve a certificate from a pool. + * @param pool Pool to fish in + * @param id ID to look up + * @return Retrieved certificate, or NULL if it wasn't there + */ +PurpleCertificate * +purple_certificate_pool_retrieve(PurpleCertificatePool *pool, const gchar *id); + +/** + * Add a certificate to a pool + * + * Any pre-existing certificate of the same ID will be overwritten. + * + * @param pool Pool to add to + * @param id ID to store the certificate with + * @param crt Certificate to store + * @return TRUE if the operation succeeded, otherwise FALSE + */ +gboolean +purple_certificate_pool_store(PurpleCertificatePool *pool, const gchar *id, PurpleCertificate *crt); + +/** + * Remove a certificate from a pool + * + * @param pool Pool to remove from + * @param id ID to remove + * @return TRUE if the operation succeeded, otherwise FALSE + */ +gboolean +purple_certificate_pool_delete(PurpleCertificatePool *pool, const gchar *id); + +/** + * Get the list of IDs currently in the pool. + * + * @param pool Pool to enumerate + * @return GList pointing to newly-allocated id strings. Free using + * purple_certificate_pool_destroy_idlist() + */ +GList * +purple_certificate_pool_get_idlist(PurpleCertificatePool *pool); + +/** + * Destroys the result given by purple_certificate_pool_get_idlist() + * + * @param idlist ID List to destroy + */ +void +purple_certificate_pool_destroy_idlist(GList *idlist); + +/*@}*/ + +/*****************************************************************************/ +/** @name Certificate Subsystem API */ +/*****************************************************************************/ +/*@{*/ + +/** + * Initialize the certificate system + */ +void +purple_certificate_init(void); + +/** + * Un-initialize the certificate system + */ +void +purple_certificate_uninit(void); + +/** + * Get the Certificate subsystem handle for signalling purposes + */ +gpointer +purple_certificate_get_handle(void); + +/** Look up a registered CertificateScheme by name + * @param name The scheme name. Case insensitive. + * @return Pointer to the located Scheme, or NULL if it isn't found. + */ +PurpleCertificateScheme * +purple_certificate_find_scheme(const gchar *name); + +/** + * Get all registered CertificateSchemes + * + * @return GList pointing to all registered CertificateSchemes . This value + * is owned by libpurple + */ +GList * +purple_certificate_get_schemes(void); + +/** Register a CertificateScheme with libpurple + * + * No two schemes can be registered with the same name; this function enforces + * that. + * + * @param scheme Pointer to the scheme to register. + * @return TRUE if the scheme was successfully added, otherwise FALSE + */ +gboolean +purple_certificate_register_scheme(PurpleCertificateScheme *scheme); + +/** Unregister a CertificateScheme from libpurple + * + * @param scheme Scheme to unregister. + * If the scheme is not registered, this is a no-op. + * + * @return TRUE if the unregister completed successfully + */ +gboolean +purple_certificate_unregister_scheme(PurpleCertificateScheme *scheme); + +/** Look up a registered PurpleCertificateVerifier by scheme and name + * @param scheme_name Scheme name. Case insensitive. + * @param ver_name The verifier name. Case insensitive. + * @return Pointer to the located Verifier, or NULL if it isn't found. + */ +PurpleCertificateVerifier * +purple_certificate_find_verifier(const gchar *scheme_name, const gchar *ver_name); + +/** + * Get the list of registered CertificateVerifiers + * + * @return GList of all registered PurpleCertificateVerifier. This value + * is owned by libpurple + */ +GList * +purple_certificate_get_verifiers(void); + +/** + * Register a CertificateVerifier with libpurple + * + * @param vr Verifier to register. + * @return TRUE if register succeeded, otherwise FALSE + */ +gboolean +purple_certificate_register_verifier(PurpleCertificateVerifier *vr); + +/** + * Unregister a CertificateVerifier with libpurple + * + * @param vr Verifier to unregister. + * @return TRUE if unregister succeeded, otherwise FALSE + */ +gboolean +purple_certificate_unregister_verifier(PurpleCertificateVerifier *vr); + +/** Look up a registered PurpleCertificatePool by scheme and name + * @param scheme_name Scheme name. Case insensitive. + * @param pool_name Pool name. Case insensitive. + * @return Pointer to the located Pool, or NULL if it isn't found. + */ +PurpleCertificatePool * +purple_certificate_find_pool(const gchar *scheme_name, const gchar *pool_name); + +/** + * Get the list of registered Pools + * + * @return GList of all registered PurpleCertificatePool s. This value + * is owned by libpurple + */ +GList * +purple_certificate_get_pools(void); + +/** + * Register a CertificatePool with libpurple and call its init function + * + * @param pool Pool to register. + * @return TRUE if the register succeeded, otherwise FALSE + */ +gboolean +purple_certificate_register_pool(PurpleCertificatePool *pool); + +/** + * Unregister a CertificatePool with libpurple and call its uninit function + * + * @param pool Pool to unregister. + * @return TRUE if the unregister succeeded, otherwise FALSE + */ +gboolean +purple_certificate_unregister_pool(PurpleCertificatePool *pool); + +/*@}*/ + + +/** + * Displays a window showing X.509 certificate information + * + * @param crt Certificate under an "x509" Scheme + * @TODO Will break on CA certs, as they have no Common Name + */ +void +purple_certificate_display_x509(PurpleCertificate *crt); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _PURPLE_CERTIFICATE_H */
--- a/libpurple/cipher.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/cipher.c Wed Sep 12 19:11:38 2007 +0000 @@ -48,7 +48,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include <glib.h> #include <string.h> @@ -2021,7 +2021,7 @@ if (client_nonce == NULL) { purple_cipher_context_destroy(context); - purple_debug_error("cipher", "Required client_nonce missing for MD5-sess digest calculation."); + purple_debug_error("cipher", "Required client_nonce missing for MD5-sess digest calculation.\n"); return NULL; } @@ -2091,7 +2091,7 @@ if (entity == NULL) { purple_cipher_context_destroy(context); - purple_debug_error("cipher", "Required entity missing for auth-int digest calculation."); + purple_debug_error("cipher", "Required entity missing for auth-int digest calculation.\n"); return NULL; } @@ -2118,14 +2118,14 @@ if (nonce_count == NULL) { purple_cipher_context_destroy(context); - purple_debug_error("cipher", "Required nonce_count missing for digest calculation."); + purple_debug_error("cipher", "Required nonce_count missing for digest calculation.\n"); return NULL; } if (client_nonce == NULL) { purple_cipher_context_destroy(context); - purple_debug_error("cipher", "Required client_nonce missing for digest calculation."); + purple_debug_error("cipher", "Required client_nonce missing for digest calculation.\n"); return NULL; }
--- a/libpurple/cipher.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/cipher.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * * @see @ref cipher-signals */
--- a/libpurple/circbuffer.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/circbuffer.c Wed Sep 12 19:11:38 2007 +0000 @@ -18,7 +18,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "internal.h"
--- a/libpurple/circbuffer.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/circbuffer.h Wed Sep 12 19:11:38 2007 +0000 @@ -18,7 +18,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _CIRCBUFFER_H #define _CIRCBUFFER_H
--- a/libpurple/cmds.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/cmds.c Wed Sep 12 19:11:38 2007 +0000 @@ -16,7 +16,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */
--- a/libpurple/cmds.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/cmds.h Wed Sep 12 19:11:38 2007 +0000 @@ -16,7 +16,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */ #ifndef _PURPLE_CMDS_H_
--- a/libpurple/connection.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/connection.c Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "internal.h" #include "account.h" @@ -158,6 +158,62 @@ } void +purple_connection_new_unregister(PurpleAccount *account, const char *password, PurpleAccountUnregistrationCb cb, void *user_data) +{ + /* Lots of copy/pasted code to avoid API changes. You might want to integrate that into the previous function when posssible. */ + PurpleConnection *gc; + PurplePlugin *prpl; + PurplePluginProtocolInfo *prpl_info; + + g_return_if_fail(account != NULL); + + prpl = purple_find_prpl(purple_account_get_protocol_id(account)); + + if (prpl != NULL) + prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl); + else { + gchar *message; + + message = g_strdup_printf(_("Missing protocol plugin for %s"), + purple_account_get_username(account)); + purple_notify_error(NULL, _("Unregistration Error"), message, NULL); + g_free(message); + return; + } + + if (!purple_account_is_disconnected(account)) { + prpl_info->unregister_user(account, cb, user_data); + return; + } + + if (((password == NULL) || (*password == '\0')) && + !(prpl_info->options & OPT_PROTO_NO_PASSWORD) && + !(prpl_info->options & OPT_PROTO_PASSWORD_OPTIONAL)) + { + purple_debug_error("connection", "Can not connect to account %s without " + "a password.\n", purple_account_get_username(account)); + return; + } + + gc = g_new0(PurpleConnection, 1); + PURPLE_DBUS_REGISTER_POINTER(gc, PurpleConnection); + + gc->prpl = prpl; + if ((password != NULL) && (*password != '\0')) + gc->password = g_strdup(password); + purple_connection_set_account(gc, account); + purple_connection_set_state(gc, PURPLE_CONNECTING); + connections = g_list_append(connections, gc); + purple_account_set_connection(account, gc); + + purple_signal_emit(purple_connections_get_handle(), "signing-on", gc); + + purple_debug_info("connection", "Unregistering. gc = %p\n", gc); + + prpl_info->unregister_user(account, cb, user_data); +} + +void purple_connection_destroy(PurpleConnection *gc) { PurpleAccount *account; @@ -436,7 +492,7 @@ g_return_if_fail(gc != NULL); if (text == NULL) { - purple_debug_error("connection", "purple_connection_error: check `text != NULL' failed"); + purple_debug_error("connection", "purple_connection_error: check `text != NULL' failed\n"); text = _("Unknown error"); }
--- a/libpurple/connection.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/connection.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * * @see @ref connection-signals */ @@ -171,6 +171,18 @@ const char *password); /** + * This function should only be called by purple_account_unregister() + * in account.c. + * + * Tries to unregister the account on the server. If the account is not + * connected, also creates a new connection. + * + * @param account The account to unregister + * @param password The password to use. + */ +void purple_connection_new_unregister(PurpleAccount *account, const char *password, PurpleAccountUnregistrationCb cb, void *user_data); + +/** * Disconnects and destroys a PurpleConnection. * * This function should only be called by purple_account_disconnect()
--- a/libpurple/conversation.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/conversation.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "internal.h" #include "blist.h" @@ -111,17 +111,17 @@ /* Always linkfy the text for display, unless we're * explicitly asked to do otheriwse*/ - if(msgflags & PURPLE_MESSAGE_NO_LINKIFY) - displayed = g_strdup(message); - else - displayed = purple_markup_linkify(message); - - if ((conv->features & PURPLE_CONNECTION_HTML) && - !(msgflags & PURPLE_MESSAGE_RAW)) - { + if (!(msgflags & PURPLE_MESSAGE_INVISIBLE)) { + if(msgflags & PURPLE_MESSAGE_NO_LINKIFY) + displayed = g_strdup(message); + else + displayed = purple_markup_linkify(message); + } + + if (displayed && (conv->features & PURPLE_CONNECTION_HTML) && + !(msgflags & PURPLE_MESSAGE_RAW)) { sent = g_strdup(displayed); - } - else + } else sent = g_strdup(message); msgflags |= PURPLE_MESSAGE_SEND; @@ -202,6 +202,48 @@ conv, time(NULL), NULL)); } +/* Functions that deal with PurpleConvMessage */ + +static void +add_message_to_history(PurpleConversation *conv, const char *who, const char *message, + PurpleMessageFlags flags, time_t when) +{ + PurpleConvMessage *msg; + + if (flags & PURPLE_MESSAGE_SEND) { + const char *me = NULL; + if (conv->account->gc) + me = conv->account->gc->display_name; + if (!me) + me = conv->account->username; + who = me; + } + + msg = g_new0(PurpleConvMessage, 1); + PURPLE_DBUS_REGISTER_POINTER(msg, PurpleConvMessage); + msg->who = g_strdup(who); + msg->flags = flags; + msg->what = g_strdup(message); + msg->when = when; + + conv->message_history = g_list_prepend(conv->message_history, msg); +} + +static void +free_conv_message(PurpleConvMessage *msg) +{ + g_free(msg->who); + g_free(msg->what); + PURPLE_DBUS_UNREGISTER_POINTER(msg); + g_free(msg); +} + +static void +message_history_free(GList *list) +{ + g_list_foreach(list, (GFunc)free_conv_message, NULL); + g_list_free(list); +} /************************************************************************** * Conversation API @@ -473,6 +515,8 @@ purple_conversation_close_logs(conv); + purple_conversation_clear_message_history(conv); + PURPLE_DBUS_UNREGISTER_POINTER(conv); g_free(conv); conv = NULL; @@ -805,9 +849,6 @@ ops = purple_conversation_get_ui_ops(conv); - if (ops == NULL || ops->write_conv == NULL) - return; - account = purple_conversation_get_account(conv); type = purple_conversation_get_type(conv); @@ -824,6 +865,10 @@ displayed = g_strdup(message); + if (who == NULL || *who == '\0') + who = purple_conversation_get_name(conv); + alias = who; + plugin_return = GPOINTER_TO_INT(purple_signal_emit_return_1( purple_conversations_get_handle(), @@ -838,11 +883,6 @@ return; } - if (who == NULL || *who == '\0') - who = purple_conversation_get_name(conv); - - alias = who; - if (account != NULL) { prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_find_prpl(purple_account_get_protocol_id(account))); @@ -891,7 +931,9 @@ } } - ops->write_conv(conv, who, alias, displayed, flags, mtime); + if (ops && ops->write_conv) + ops->write_conv(conv, who, alias, displayed, flags, mtime); + add_message_to_history(conv, who, message, flags, mtime); purple_signal_emit(purple_conversations_get_handle(), (type == PURPLE_CONV_TYPE_IM ? "wrote-im-msg" : "wrote-chat-msg"), @@ -2020,6 +2062,42 @@ return menu; } +void purple_conversation_clear_message_history(PurpleConversation *conv) +{ + GList *list = conv->message_history; + message_history_free(list); + conv->message_history = NULL; +} + +GList *purple_conversation_get_message_history(PurpleConversation *conv) +{ + return conv->message_history; +} + +const char *purple_conversation_message_get_sender(PurpleConvMessage *msg) +{ + g_return_val_if_fail(msg, NULL); + return msg->who; +} + +const char *purple_conversation_message_get_message(PurpleConvMessage *msg) +{ + g_return_val_if_fail(msg, NULL); + return msg->what; +} + +PurpleMessageFlags purple_conversation_message_get_flags(PurpleConvMessage *msg) +{ + g_return_val_if_fail(msg, 0); + return msg->flags; +} + +time_t purple_conversation_message_get_timestamp(PurpleConvMessage *msg) +{ + g_return_val_if_fail(msg, 0); + return msg->when; +} + gboolean purple_conversation_do_command(PurpleConversation *conv, const gchar *cmdline, const gchar *markup, gchar **error) @@ -2309,3 +2387,4 @@ purple_conversation_destroy((PurpleConversation*)conversations->data); purple_signals_unregister_by_instance(purple_conversations_get_handle()); } +
--- a/libpurple/conversation.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/conversation.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * * @see @ref conversation-signals */ @@ -37,6 +37,7 @@ typedef struct _PurpleConvIm PurpleConvIm; typedef struct _PurpleConvChat PurpleConvChat; typedef struct _PurpleConvChatBuddy PurpleConvChatBuddy; +typedef struct _PurpleConvMessage PurpleConvMessage; /** * A type of conversation. @@ -117,9 +118,9 @@ apply formatting */ PURPLE_MESSAGE_IMAGES = 0x1000, /**< Message contains images */ PURPLE_MESSAGE_NOTIFY = 0x2000, /**< Message is a notification */ - PURPLE_MESSAGE_NO_LINKIFY = 0x4000 /**< Message should not be auto- + PURPLE_MESSAGE_NO_LINKIFY = 0x4000, /**< Message should not be auto- linkified */ - + PURPLE_MESSAGE_INVISIBLE = 0x8000, /**< Message should not be displayed */ } PurpleMessageFlags; /** @@ -183,7 +184,7 @@ time_t mtime); /** Add @a cbuddies to a chat. - * @param cbuddies A @C GList of #PurpleConvChatBuddy structs. + * @param cbuddies A @c GList of #PurpleConvChatBuddy structs. * @param new_arrivals Whether join notices should be shown. * (Join notices are actually written to the * conversation by #purple_conv_chat_add_users().) @@ -199,7 +200,7 @@ void (*chat_rename_user)(PurpleConversation *conv, const char *old_name, const char *new_name, const char *new_alias); /** Remove @a users from a chat. - * @param users A @C GList of <tt>const char *</tt>s. + * @param users A @c GList of <tt>const char *</tt>s. * @see purple_conv_chat_rename_user() */ void (*chat_remove_users)(PurpleConversation *conv, GList *users); @@ -283,6 +284,17 @@ }; /** + * Description of a conversation message + */ +struct _PurpleConvMessage +{ + char *who; + char *what; + PurpleMessageFlags flags; + time_t when; +}; + +/** * A core representation of a conversation between two or more people. * * The conversation can be an IM or a chat. @@ -315,7 +327,7 @@ GHashTable *data; /**< Plugin-specific data. */ PurpleConnectionFlags features; /**< The supported features */ - + GList *message_history; /**< Message history, as a GList of PurpleConvMessage's */ }; #ifdef __cplusplus @@ -650,6 +662,60 @@ */ void purple_conversation_foreach(void (*func)(PurpleConversation *conv)); +/** + * Retrieve the message history of a conversation. + * + * @param conv The conversation + * + * @return A GList of PurpleConvMessage's. The must not modify the list or the data within. + * The list contains the newest message at the beginning, and the oldest message at + * the end. + */ +GList *purple_conversation_get_message_history(PurpleConversation *conv); + +/** + * Clear the message history of a conversation. + * + * @param conv The conversation + */ +void purple_conversation_clear_message_history(PurpleConversation *conv); + +/** + * Get the sender from a PurpleConvMessage + * + * @param msg A PurpleConvMessage + * + * @return The name of the sender of the message + */ +const char *purple_conversation_message_get_sender(PurpleConvMessage *msg); + +/** + * Get the message from a PurpleConvMessage + * + * @param msg A PurpleConvMessage + * + * @return The name of the sender of the message + */ +const char *purple_conversation_message_get_message(PurpleConvMessage *msg); + +/** + * Get the message-flags of a PurpleConvMessage + * + * @param msg A PurpleConvMessage + * + * @return The name of the sender of the message + */ +PurpleMessageFlags purple_conversation_message_get_flags(PurpleConvMessage *msg); + +/** + * Get the timestamp of a PurpleConvMessage + * + * @param msg A PurpleConvMessage + * + * @return The name of the sender of the message + */ +time_t purple_conversation_message_get_timestamp(PurpleConvMessage *msg); + /*@}*/
--- a/libpurple/core.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/core.c Wed Sep 12 19:11:38 2007 +0000 @@ -20,10 +20,11 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "internal.h" #include "cipher.h" +#include "certificate.h" #include "connection.h" #include "conversation.h" #include "core.h" @@ -141,6 +142,7 @@ purple_accounts_init(); purple_savedstatuses_init(); purple_notify_init(); + purple_certificate_init(); purple_connections_init(); purple_conversations_init(); purple_blist_init(); @@ -159,9 +161,8 @@ /* * Call this early on to try to auto-detect our IP address and * hopefully save some time later. - * TODO: do this here after purple_prefs_load() has been moved into purple_prefs_init() */ - /*purple_network_get_my_ip(-1);*/ + purple_network_get_my_ip(-1); if (ops != NULL && ops->ui_init != NULL) ops->ui_init(); @@ -192,6 +193,7 @@ purple_notify_uninit(); purple_conversations_uninit(); purple_connections_uninit(); + purple_certificate_uninit(); purple_buddy_icons_uninit(); purple_accounts_uninit(); purple_savedstatuses_uninit();
--- a/libpurple/core.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/core.h Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * * @see @ref core-signals */
--- a/libpurple/dbus-analyze-functions.py Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/dbus-analyze-functions.py Wed Sep 12 19:11:38 2007 +0000 @@ -26,6 +26,11 @@ "purple_conv_placement_get_current_func", "purple_conv_placement_set_current_func", + # Similar to the above: + "purple_account_set_register_callback", + "purple_account_unregister", + "purple_connection_new_unregister", + # This is excluded because this script treats PurpleLogReadFlags* # as pointer to a struct, instead of a pointer to an enum. This # causes a compilation error. Someone should fix this script. @@ -66,6 +71,7 @@ "purple_savedstatuses_get_all", "purple_status_type_get_attrs", "purple_presence_get_statuses", + "purple_conversation_get_message_history", ] pointer = "#pointer#"
--- a/libpurple/dbus-bindings.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/dbus-bindings.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */
--- a/libpurple/dbus-purple.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/dbus-purple.h Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */
--- a/libpurple/dbus-server.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/dbus-server.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */
--- a/libpurple/dbus-server.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/dbus-server.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * * @see @ref dbus-server-signals */
--- a/libpurple/debug.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/debug.c Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "debug.h" #include "internal.h"
--- a/libpurple/debug.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/debug.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _PURPLE_DEBUG_H_ #define _PURPLE_DEBUG_H_
--- a/libpurple/desktopitem.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/desktopitem.c Wed Sep 12 19:11:38 2007 +0000 @@ -18,7 +18,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */ @@ -48,8 +48,8 @@ * * You should have received a copy of the GNU Library General Public * License along with the Gnome Library; see the file COPYING.LIB. If not, - * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. + * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02111-1301, USA. */ #include <errno.h>
--- a/libpurple/desktopitem.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/desktopitem.h Wed Sep 12 19:11:38 2007 +0000 @@ -18,7 +18,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */ @@ -48,8 +48,8 @@ * * You should have received a copy of the GNU Library General Public * License along with the Gnome Library; see the file COPYING.LIB. If not, - * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. + * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02111-1301, USA. */ #ifndef _PURPLE_DESKTOP_ITEM_H_
--- a/libpurple/dnsquery.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/dnsquery.c Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */
--- a/libpurple/dnsquery.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/dnsquery.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _PURPLE_DNSQUERY_H_ #define _PURPLE_DNSQUERY_H_ @@ -45,16 +45,21 @@ typedef void (*PurpleDnsQueryFailedCallback) (PurpleDnsQueryData *query_data, const gchar *error_message); /** - * DNS Request UI operations + * DNS Request UI operations; UIs should implement this if they want to do DNS + * lookups themselves, rather than relying on the core. + * + * @see @ref ui-ops */ typedef struct { - /* If implemented, the UI is responsible for DNS queries */ - gboolean (*resolve_host)(PurpleDnsQueryData *query_data, PurpleDnsQueryResolvedCallback resolved_cb, PurpleDnsQueryFailedCallback failed_cb); + /** If implemented, the UI is responsible for DNS queries */ + gboolean (*resolve_host)(PurpleDnsQueryData *query_data, + PurpleDnsQueryResolvedCallback resolved_cb, + PurpleDnsQueryFailedCallback failed_cb); - /* After destroy is called, query_data will be feed, so this must - * cancel any further use of it the UI would do. Unneeded if - * resolve_host is not implemented. + /** Called just before @a query_data is freed; this should cancel any + * further use of @q query_data the UI would make. Unneeded if + * #resolve_host is not implemented. */ void (*destroy)(PurpleDnsQueryData *query_data);
--- a/libpurple/dnssrv.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/dnssrv.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "internal.h"
--- a/libpurple/dnssrv.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/dnssrv.h Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _PURPLE_DNSSRV_H
--- a/libpurple/eventloop.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/eventloop.c Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "eventloop.h" #include "internal.h"
--- a/libpurple/eventloop.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/eventloop.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _PURPLE_EVENTLOOP_H_ #define _PURPLE_EVENTLOOP_H_
--- a/libpurple/example/nullclient.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/example/nullclient.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */
--- a/libpurple/ft.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/ft.c Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */ #include "internal.h"
--- a/libpurple/ft.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/ft.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * * @see @ref xfer-signals */
--- a/libpurple/gaim-compat.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/gaim-compat.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * * @see @ref account-signals */ @@ -1654,7 +1654,7 @@ #define gaim_request_choice_varg purple_request_choice_varg #define gaim_request_action purple_request_action #define gaim_request_action_varg purple_request_action_varg -#define gaim_request_fields purple_request_fields +#define gaim_request_fields(handle, title, primary, secondary, fields, ok_text, ok_cb, cancel_text, cancel_cb, user_data) purple_request_fields(handle, title, primary, secondary, fields, ok_text, ok_cb, cancel_text, cancel_cb, NULL, NULL, NULL, user_data) #define gaim_request_close purple_request_close #define gaim_request_close_with_handle purple_request_close_with_handle
--- a/libpurple/idle.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/idle.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */ #include "internal.h" @@ -163,8 +163,8 @@ { if (!no_away) { + no_away = TRUE; purple_savedstatus_set_idleaway(FALSE); - no_away = TRUE; } time_until_next_idle_event = 0; return;
--- a/libpurple/idle.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/idle.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _PURPLE_IDLE_H_ #define _PURPLE_IDLE_H_
--- a/libpurple/imgstore.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/imgstore.c Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */
--- a/libpurple/imgstore.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/imgstore.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * * @see @ref imgstore-signals */
--- a/libpurple/internal.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/internal.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _PURPLE_INTERNAL_H_ #define _PURPLE_INTERNAL_H_ @@ -29,6 +29,10 @@ # include <config.h> #endif +/* for SIOCGIFCONF in SKYOS */ +#ifdef SKYOS +#include <net/sockios.h> +#endif /* * If we're using NLS, make sure gettext works. If not, then define * dummy macros in place of the normal gettext macros.
--- a/libpurple/log.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/log.c Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "internal.h" @@ -1380,7 +1380,7 @@ written += fprintf(data->file, "<font color=\"#16569E\"><font size=\"2\">(%s)</font> <b>%s:</b></font> %s<br/>\n", date, from, msg_fixed); } else { - purple_debug_error("log", "Unhandled message type."); + purple_debug_error("log", "Unhandled message type.\n"); written += fprintf(data->file, "<font size=\"2\">(%s)</font><b> %s:</b></font> %s<br/>\n", date, from, msg_fixed); }
--- a/libpurple/log.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/log.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * * @see @ref log-signals */
--- a/libpurple/mime.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/mime.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, * USA. */
--- a/libpurple/mime.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/mime.h Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, * USA. */
--- a/libpurple/nat-pmp.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/nat-pmp.c Wed Sep 12 19:11:38 2007 +0000 @@ -175,20 +175,20 @@ /* Determine the buffer side needed to get the full routing table */ if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) { - purple_debug_warning("nat-pmp", "sysctl: net.route.0.0.dump estimate"); + purple_debug_warning("nat-pmp", "sysctl: net.route.0.0.dump estimate\n"); return NULL; } if (!(buf = malloc(needed))) { - purple_debug_warning("nat-pmp", "malloc"); + purple_debug_warning("nat-pmp", "malloc\n"); return NULL; } /* Read the routing table into buf */ if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) { - purple_debug_warning("nat-pmp", "sysctl: net.route.0.0.dump"); + purple_debug_warning("nat-pmp", "sysctl: net.route.0.0.dump\n"); return NULL; } @@ -231,7 +231,7 @@ sin->sin_addr.s_addr = rti_sin->sin_addr.s_addr; memcpy(sin, rti_info[RTAX_GATEWAY], sizeof(struct sockaddr_in)); - purple_debug_info("nat-pmp", "found a default gateway"); + purple_debug_info("nat-pmp", "found a default gateway\n"); found = TRUE; break; } @@ -266,7 +266,7 @@ if ((pmp_info.status == PURPLE_PMP_STATUS_DISCOVERED) && (pmp_info.publicip != NULL)) { #ifdef PMP_DEBUG - purple_debug_info("nat-pmp", "Returning cached publicip %s",pmp_info.publicip); + purple_debug_info("nat-pmp", "Returning cached publicip %s\n",pmp_info.publicip); #endif return pmp_info.publicip; }
--- a/libpurple/network.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/network.c Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "internal.h" @@ -351,12 +351,13 @@ listen_data->retry = TRUE; listen_data->cb = cb; listen_data->cb_data = cb_data; + listen_data->socket_type = socket_type; /* Attempt a NAT-PMP Mapping, which will return immediately */ if (purple_pmp_create_map(((socket_type == SOCK_STREAM) ? PURPLE_PMP_TYPE_TCP : PURPLE_PMP_TYPE_UDP), actual_port, actual_port, PURPLE_PMP_LIFETIME)) { - purple_debug_info("network", "Created NAT-PMP mapping on port %i",actual_port); + purple_debug_info("network", "Created NAT-PMP mapping on port %i\n",actual_port); /* We want to return listen_data now, and on the next run loop trigger the cb and destroy listen_data */ purple_timeout_add(0, purple_network_finish_pmp_map_cb, listen_data); } @@ -436,7 +437,7 @@ static gint wpurple_get_connected_network_count(void) { - guint net_cnt = 0; + gint net_cnt = 0; WSAQUERYSET qs; HANDLE h; @@ -521,7 +522,7 @@ HANDLE hLookup, DWORD dwControlCode, LPVOID lpvInBuffer, DWORD cbInBuffer, LPVOID lpvOutBuffer, DWORD cbOutBuffer, LPDWORD lpcbBytesReturned, LPWSACOMPLETION lpCompletion) = NULL; - + if (!(MyWSANSPIoctl = (void*) wpurple_find_and_loadproc("ws2_32.dll", "WSANSPIoctl"))) { g_thread_exit(NULL); return NULL; @@ -636,7 +637,7 @@ purple_network_get_handle(void) { static int handle; - + return &handle; } @@ -675,7 +676,7 @@ purple_signal_register(purple_network_get_handle(), "network-configuration-changed", purple_marshal_VOID, NULL, 0); - + purple_pmp_init(); purple_upnp_init(); }
--- a/libpurple/network.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/network.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _PURPLE_NETWORK_H_ #define _PURPLE_NETWORK_H_
--- a/libpurple/notify.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/notify.c Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "internal.h" #include "dbus-maybe.h"
--- a/libpurple/notify.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/notify.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * * @see @ref notify-signals */ @@ -213,6 +213,11 @@ PurpleNotifySearchResults *results, PurpleNotifyCloseCallback cb, gpointer user_data); +/** + * Frees a PurpleNotifySearchResults object. + * + * @param results The PurpleNotifySearchResults to free. + */ void purple_notify_searchresults_free(PurpleNotifySearchResults *results); /**
--- a/libpurple/ntlm.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/ntlm.c Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include <glib.h>
--- a/libpurple/ntlm.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/ntlm.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _PURPLE_NTLM_H
--- a/libpurple/plugin.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/plugin.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "internal.h"
--- a/libpurple/plugin.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/plugin.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * * @see @ref plugin-signals * @see @ref plugin-ids @@ -188,6 +188,8 @@ /** NULL for plugin actions menu, set to the PurpleConnection for account actions menu */ gpointer context; + + gpointer user_data; }; #define PURPLE_PLUGIN_HAS_ACTIONS(plugin) \
--- a/libpurple/pluginpref.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/pluginpref.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifdef HAVE_CONFIG_H # include <config.h>
--- a/libpurple/pluginpref.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/pluginpref.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */ #ifndef _PURPLE_PLUGINPREF_H_
--- a/libpurple/plugins/autoaccept.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/plugins/autoaccept.c Wed Sep 12 19:11:38 2007 +0000 @@ -14,8 +14,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02111-1301, USA. */ #include "internal.h"
--- a/libpurple/plugins/buddynote.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/plugins/buddynote.c Wed Sep 12 19:11:38 2007 +0000 @@ -14,7 +14,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA. */ #include "internal.h"
--- a/libpurple/plugins/ciphertest.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/plugins/ciphertest.c Wed Sep 12 19:11:38 2007 +0000 @@ -15,8 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02111-1301, USA. */ #ifdef HAVE_CONFIG_H
--- a/libpurple/plugins/codeinline.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/plugins/codeinline.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "internal.h"
--- a/libpurple/plugins/dbus-buddyicons-example.py Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/plugins/dbus-buddyicons-example.py Wed Sep 12 19:11:38 2007 +0000 @@ -18,7 +18,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA # import dbus
--- a/libpurple/plugins/dbus-example.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/plugins/dbus-example.c Wed Sep 12 19:11:38 2007 +0000 @@ -32,7 +32,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "internal.h"
--- a/libpurple/plugins/fortuneprofile.pl Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/plugins/fortuneprofile.pl Wed Sep 12 19:11:38 2007 +0000 @@ -40,7 +40,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA use Gaim;
--- a/libpurple/plugins/idle.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/plugins/idle.c Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "internal.h"
--- a/libpurple/plugins/ipc-test-client.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/plugins/ipc-test-client.c Wed Sep 12 19:11:38 2007 +0000 @@ -15,8 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02111-1301, USA. */ #include "internal.h" #include "debug.h"
--- a/libpurple/plugins/ipc-test-server.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/plugins/ipc-test-server.c Wed Sep 12 19:11:38 2007 +0000 @@ -15,8 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02111-1301, USA. */ #define IPC_TEST_SERVER_PLUGIN_ID "core-ipc-test-server"
--- a/libpurple/plugins/joinpart.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/plugins/joinpart.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "internal.h"
--- a/libpurple/plugins/newline.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/plugins/newline.c Wed Sep 12 19:11:38 2007 +0000 @@ -14,7 +14,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA. */ #include "internal.h"
--- a/libpurple/plugins/offlinemsg.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/plugins/offlinemsg.c Wed Sep 12 19:11:38 2007 +0000 @@ -14,8 +14,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02111-1301, USA. */ #include "internal.h" @@ -113,6 +113,10 @@ PurpleConversation *conv; OfflineMessageSetting setting; + if (message == NULL || *message == NULL || + **message == '\0') + return; + buddy = purple_find_buddy(account, who); if (!buddy) return; @@ -122,7 +126,7 @@ if (purple_account_supports_offline_message(account, buddy)) { - purple_debug_info("offlinemsg", "Account \"%s\" supports offline message.", + purple_debug_info("offlinemsg", "Account \"%s\" supports offline messages.\n", purple_account_get_username(account)); return; } @@ -167,8 +171,8 @@ static gboolean plugin_load(PurplePlugin *plugin) { - purple_signal_connect(purple_conversations_get_handle(), "sending-im-msg", - plugin, PURPLE_CALLBACK(sending_msg_cb), plugin); + purple_signal_connect_priority(purple_conversations_get_handle(), "sending-im-msg", + plugin, PURPLE_CALLBACK(sending_msg_cb), plugin, PURPLE_SIGNAL_PRIORITY_HIGHEST); return TRUE; }
--- a/libpurple/plugins/perl/perl.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/plugins/perl/perl.c Wed Sep 12 19:11:38 2007 +0000 @@ -15,7 +15,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifdef HAVE_CONFIG_H #include <config.h>
--- a/libpurple/plugins/pluginpref_example.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/plugins/pluginpref_example.c Wed Sep 12 19:11:38 2007 +0000 @@ -15,8 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02111-1301, USA. */ #ifdef HAVE_CONFIG_H
--- a/libpurple/plugins/psychic.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/plugins/psychic.c Wed Sep 12 19:11:38 2007 +0000 @@ -49,7 +49,7 @@ } if(FALSE == purple_privacy_check(acct, name)) { - purple_debug_info("psychic", "user %s is blocked", name); + purple_debug_info("psychic", "user %s is blocked\n", name); return; }
--- a/libpurple/plugins/signals-test.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/plugins/signals-test.c Wed Sep 12 19:11:38 2007 +0000 @@ -15,8 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02111-1301, USA. */ #define SIGNAL_TEST_PLUGIN_ID "core-signals-test"
--- a/libpurple/plugins/ssl/Makefile.mingw Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/plugins/ssl/Makefile.mingw Wed Sep 12 19:11:38 2007 +0000 @@ -18,6 +18,7 @@ $(NSS_TOP)/lib/nss3.dll \ $(NSS_TOP)/lib/nssckbi.dll \ $(NSS_TOP)/lib/softokn3.dll \ + $(NSS_TOP)/lib/smime3.dll \ $(NSS_TOP)/lib/ssl3.dll \ $(NSPR_TOP)/lib/nspr4.dll \ $(NSPR_TOP)/lib/plc4.dll \ @@ -59,7 +60,8 @@ -lpurple \ -lnss3 \ -lnspr4 \ - -lssl3 + -lssl3 \ + -lsmime3 include $(PIDGIN_COMMON_RULES)
--- a/libpurple/plugins/ssl/ssl-gnutls.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/plugins/ssl/ssl-gnutls.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,19 +17,22 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "internal.h" #include "debug.h" +#include "certificate.h" #include "plugin.h" #include "sslconn.h" #include "version.h" +#include "util.h" #define SSL_GNUTLS_PLUGIN_ID "ssl-gnutls" #ifdef HAVE_GNUTLS #include <gnutls/gnutls.h> +#include <gnutls/x509.h> typedef struct { @@ -44,9 +47,25 @@ static void ssl_gnutls_init_gnutls(void) { + /* Configure GnuTLS to use glib memory management */ + /* I expect that this isn't really necessary, but it may prevent + some bugs */ + /* TODO: It may be necessary to wrap this allocators for GnuTLS. + If there are strange bugs, perhaps look here (yes, I am a + hypocrite) */ + gnutls_global_set_mem_functions( + (gnutls_alloc_function) g_malloc0, /* malloc */ + (gnutls_alloc_function) g_malloc0, /* secure malloc */ + NULL, /* mem_is_secure */ + (gnutls_realloc_function) g_realloc, /* realloc */ + (gnutls_free_function) g_free /* free */ + ); + gnutls_global_init(); gnutls_certificate_allocate_credentials(&xcred); + + /* TODO: I can likely remove this */ gnutls_certificate_set_x509_trust_file(xcred, "ca.pem", GNUTLS_X509_FMT_PEM); } @@ -54,7 +73,7 @@ static gboolean ssl_gnutls_init(void) { - return TRUE; + return TRUE; } static void @@ -65,6 +84,25 @@ gnutls_certificate_free_credentials(xcred); } +static void +ssl_gnutls_verified_cb(PurpleCertificateVerificationStatus st, + gpointer userdata) +{ + PurpleSslConnection *gsc = (PurpleSslConnection *) userdata; + + if (st == PURPLE_CERTIFICATE_VALID) { + /* Certificate valid? Good! Do the connection! */ + gsc->connect_cb(gsc->connect_cb_data, gsc, PURPLE_INPUT_READ); + } else { + /* Otherwise, signal an error */ + if(gsc->error_cb != NULL) + gsc->error_cb(gsc, PURPLE_SSL_CERTIFICATE_INVALID, + gsc->connect_cb_data); + purple_ssl_close(gsc); + } +} + + static void ssl_gnutls_handshake_cb(gpointer data, gint source, PurpleInputCondition cond) @@ -73,7 +111,7 @@ PurpleSslGnutlsData *gnutls_data = PURPLE_SSL_GNUTLS_DATA(gsc); ssize_t ret; - purple_debug_info("gnutls", "Handshaking\n"); + purple_debug_info("gnutls", "Handshaking with %s\n", gsc->host); ret = gnutls_handshake(gnutls_data->session); if(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED) @@ -92,9 +130,119 @@ purple_ssl_close(gsc); } else { + /* Now we are cooking with gas! */ + PurpleSslOps *ops = purple_ssl_get_ops(); + GList * peers = ops->get_peer_certificates(gsc); + + PurpleCertificateScheme *x509 = + purple_certificate_find_scheme("x509"); + + GList * l; + + /* TODO: Remove all this debugging babble */ purple_debug_info("gnutls", "Handshake complete\n"); - gsc->connect_cb(gsc->connect_cb_data, gsc, cond); + for (l=peers; l; l = l->next) { + PurpleCertificate *crt = l->data; + GByteArray *z = + x509->get_fingerprint_sha1(crt); + gchar * fpr = + purple_base16_encode_chunked(z->data, + z->len); + + purple_debug_info("gnutls/x509", + "Key print: %s\n", + fpr); + + /* Kill the cert! */ + x509->destroy_certificate(crt); + + g_free(fpr); + g_byte_array_free(z, TRUE); + } + g_list_free(peers); + + { + const gnutls_datum_t *cert_list; + unsigned int cert_list_size = 0; + gnutls_session_t session=gnutls_data->session; + int i; + + cert_list = + gnutls_certificate_get_peers(session, &cert_list_size); + + purple_debug_info("gnutls", + "Peer provided %d certs\n", + cert_list_size); + for (i=0; i<cert_list_size; i++) + { + gchar fpr_bin[256]; + gsize fpr_bin_sz = sizeof(fpr_bin); + gchar * fpr_asc = NULL; + gchar tbuf[256]; + gsize tsz=sizeof(tbuf); + gchar * tasc = NULL; + gnutls_x509_crt_t cert; + + gnutls_x509_crt_init(&cert); + gnutls_x509_crt_import (cert, &cert_list[i], + GNUTLS_X509_FMT_DER); + + gnutls_x509_crt_get_fingerprint(cert, GNUTLS_MAC_SHA, + fpr_bin, &fpr_bin_sz); + + fpr_asc = + purple_base16_encode_chunked((const guchar *)fpr_bin, fpr_bin_sz); + + purple_debug_info("gnutls", + "Lvl %d SHA1 fingerprint: %s\n", + i, fpr_asc); + + tsz=sizeof(tbuf); + gnutls_x509_crt_get_serial(cert,tbuf,&tsz); + tasc=purple_base16_encode_chunked((const guchar *)tbuf, tsz); + purple_debug_info("gnutls", + "Serial: %s\n", + tasc); + g_free(tasc); + + tsz=sizeof(tbuf); + gnutls_x509_crt_get_dn (cert, tbuf, &tsz); + purple_debug_info("gnutls", + "Cert DN: %s\n", + tbuf); + tsz=sizeof(tbuf); + gnutls_x509_crt_get_issuer_dn (cert, tbuf, &tsz); + purple_debug_info("gnutls", + "Cert Issuer DN: %s\n", + tbuf); + + g_free(fpr_asc); + fpr_asc = NULL; + gnutls_x509_crt_deinit(cert); + } + } + + /* TODO: The following logic should really be in libpurple */ + /* If a Verifier was given, hand control over to it */ + if (gsc->verifier) { + GList *peers; + /* First, get the peer cert chain */ + peers = purple_ssl_get_peer_certificates(gsc); + + /* Now kick off the verification process */ + purple_certificate_verify(gsc->verifier, + gsc->host, + peers, + ssl_gnutls_verified_cb, + gsc); + + purple_certificate_destroy_list(peers); + } else { + /* Otherwise, just call the "connection complete" + callback */ + gsc->connect_cb(gsc->connect_cb_data, gsc, cond); + } } } @@ -213,6 +361,565 @@ return s; } +/* Forward declarations are fun! */ +static PurpleCertificate * +x509_import_from_datum(const gnutls_datum_t dt, gnutls_x509_crt_fmt_t mode); + +static GList * +ssl_gnutls_get_peer_certificates(PurpleSslConnection * gsc) +{ + PurpleSslGnutlsData *gnutls_data = PURPLE_SSL_GNUTLS_DATA(gsc); + + /* List of Certificate instances to return */ + GList * peer_certs = NULL; + + /* List of raw certificates as given by GnuTLS */ + const gnutls_datum_t *cert_list; + unsigned int cert_list_size = 0; + + unsigned int i; + + /* This should never, ever happen. */ + g_return_val_if_fail( gnutls_certificate_type_get (gnutls_data->session) == GNUTLS_CRT_X509, NULL); + + /* Get the certificate list from GnuTLS */ + /* TODO: I am _pretty sure_ this doesn't block or do other exciting things */ + cert_list = gnutls_certificate_get_peers(gnutls_data->session, + &cert_list_size); + + /* Convert each certificate to a Certificate and append it to the list */ + for (i = 0; i < cert_list_size; i++) { + PurpleCertificate * newcrt = x509_import_from_datum(cert_list[i], + GNUTLS_X509_FMT_DER); + /* Append is somewhat inefficient on linked lists, but is easy + to read. If someone complains, I'll change it. + TODO: Is anyone complaining? (Maybe elb?) */ + peer_certs = g_list_append(peer_certs, newcrt); + } + + /* cert_list doesn't need free()-ing */ + + return peer_certs; +} + +/************************************************************************/ +/* X.509 functionality */ +/************************************************************************/ +const gchar * SCHEME_NAME = "x509"; + +static PurpleCertificateScheme x509_gnutls; + +/** Refcounted GnuTLS certificate data instance */ +typedef struct { + gint refcount; + gnutls_x509_crt_t crt; +} x509_crtdata_t; + +/** Helper functions for reference counting */ +static x509_crtdata_t * +x509_crtdata_addref(x509_crtdata_t *cd) +{ + (cd->refcount)++; + return cd; +} + +static void +x509_crtdata_delref(x509_crtdata_t *cd) +{ + (cd->refcount)--; + + if (cd->refcount < 0) + g_critical("Refcount of x509_crtdata_t is %d, which is less " + "than zero!\n", cd->refcount); + + /* If the refcount reaches zero, kill the structure */ + if (cd->refcount <= 0) { + purple_debug_info("gnutls/x509", + "Freeing unused cert data at %p\n", + cd); + /* Kill the internal data */ + gnutls_x509_crt_deinit( cd->crt ); + /* And kill the struct */ + g_free( cd ); + } +} + +/** Helper macro to retrieve the GnuTLS crt_t from a PurpleCertificate */ +#define X509_GET_GNUTLS_DATA(pcrt) ( ((x509_crtdata_t *) (pcrt->data))->crt) + +/** Transforms a gnutls_datum_t containing an X.509 certificate into a Certificate instance under the x509_gnutls scheme + * + * @param dt Datum to transform + * @param mode GnuTLS certificate format specifier (GNUTLS_X509_FMT_PEM for + * reading from files, and GNUTLS_X509_FMT_DER for converting + * "over the wire" certs for SSL) + * + * @return A newly allocated Certificate structure of the x509_gnutls scheme + */ +static PurpleCertificate * +x509_import_from_datum(const gnutls_datum_t dt, gnutls_x509_crt_fmt_t mode) +{ + /* Internal certificate data structure */ + x509_crtdata_t *certdat; + /* New certificate to return */ + PurpleCertificate * crt; + + /* Allocate and prepare the internal certificate data */ + certdat = g_new0(x509_crtdata_t, 1); + gnutls_x509_crt_init(&(certdat->crt)); + certdat->refcount = 0; + + /* Perform the actual certificate parse */ + /* Yes, certdat->crt should be passed as-is */ + gnutls_x509_crt_import(certdat->crt, &dt, mode); + + /* Allocate the certificate and load it with data */ + crt = g_new0(PurpleCertificate, 1); + crt->scheme = &x509_gnutls; + crt->data = x509_crtdata_addref(certdat); + + return crt; +} + +/** Imports a PEM-formatted X.509 certificate from the specified file. + * @param filename Filename to import from. Format is PEM + * + * @return A newly allocated Certificate structure of the x509_gnutls scheme + */ +static PurpleCertificate * +x509_import_from_file(const gchar * filename) +{ + PurpleCertificate *crt; /* Certificate being constructed */ + gchar *buf; /* Used to load the raw file data */ + gsize buf_sz; /* Size of the above */ + gnutls_datum_t dt; /* Struct to pass down to GnuTLS */ + + purple_debug_info("gnutls", + "Attempting to load X.509 certificate from %s\n", + filename); + + /* Next, we'll simply yank the entire contents of the file + into memory */ + /* TODO: Should I worry about very large files here? */ + g_return_val_if_fail( + g_file_get_contents(filename, + &buf, + &buf_sz, + NULL /* No error checking for now */ + ), + NULL); + + /* Load the datum struct */ + dt.data = (unsigned char *) buf; + dt.size = buf_sz; + + /* Perform the conversion */ + crt = x509_import_from_datum(dt, + GNUTLS_X509_FMT_PEM); // files should be in PEM format + + /* Cleanup */ + g_free(buf); + + return crt; +} + +/** + * Exports a PEM-formatted X.509 certificate to the specified file. + * @param filename Filename to export to. Format will be PEM + * @param crt Certificate to export + * + * @return TRUE if success, otherwise FALSE + */ +static gboolean +x509_export_certificate(const gchar *filename, PurpleCertificate *crt) +{ + gnutls_x509_crt_t crt_dat; /* GnuTLS cert struct */ + int ret; + gchar * out_buf; /* Data to output */ + size_t out_size; /* Output size */ + gboolean success = FALSE; + + /* Paranoia paranoia paranoia! */ + g_return_val_if_fail(filename, FALSE); + g_return_val_if_fail(crt, FALSE); + g_return_val_if_fail(crt->scheme == &x509_gnutls, FALSE); + g_return_val_if_fail(crt->data, FALSE); + + crt_dat = X509_GET_GNUTLS_DATA(crt); + + /* Obtain the output size required */ + out_size = 0; + ret = gnutls_x509_crt_export(crt_dat, GNUTLS_X509_FMT_PEM, + NULL, /* Provide no buffer yet */ + &out_size /* Put size here */ + ); + g_return_val_if_fail(ret == GNUTLS_E_SHORT_MEMORY_BUFFER, FALSE); + + /* Now allocate a buffer and *really* export it */ + out_buf = g_new0(gchar, out_size); + ret = gnutls_x509_crt_export(crt_dat, GNUTLS_X509_FMT_PEM, + out_buf, /* Export to our new buffer */ + &out_size /* Put size here */ + ); + if (ret != 0) { + purple_debug_error("gnutls/x509", + "Failed to export cert to buffer with code %d\n", + ret); + g_free(out_buf); + return FALSE; + } + + /* Write it out to an actual file */ + success = purple_util_write_data_to_file_absolute(filename, + out_buf, out_size); + + g_free(out_buf); + g_return_val_if_fail(success, FALSE); + return success; +} + +static PurpleCertificate * +x509_copy_certificate(PurpleCertificate *crt) +{ + x509_crtdata_t *crtdat; + PurpleCertificate *newcrt; + + g_return_val_if_fail(crt, NULL); + g_return_val_if_fail(crt->scheme == &x509_gnutls, NULL); + + crtdat = (x509_crtdata_t *) crt->data; + + newcrt = g_new0(PurpleCertificate, 1); + newcrt->scheme = &x509_gnutls; + newcrt->data = x509_crtdata_addref(crtdat); + + return newcrt; +} +/** Frees a Certificate + * + * Destroys a Certificate's internal data structures and frees the pointer + * given. + * @param crt Certificate instance to be destroyed. It WILL NOT be destroyed + * if it is not of the correct CertificateScheme. Can be NULL + * + */ +static void +x509_destroy_certificate(PurpleCertificate * crt) +{ + if (NULL == crt) return; + + /* Check that the scheme is x509_gnutls */ + if ( crt->scheme != &x509_gnutls ) { + purple_debug_error("gnutls", + "destroy_certificate attempted on certificate of wrong scheme (scheme was %s, expected %s)\n", + crt->scheme->name, + SCHEME_NAME); + return; + } + + g_return_if_fail(crt->data != NULL); + g_return_if_fail(crt->scheme != NULL); + + /* Use the reference counting system to free (or not) the + underlying data */ + x509_crtdata_delref((x509_crtdata_t *)crt->data); + + /* Kill the structure itself */ + g_free(crt); +} + +/** Determines whether one certificate has been issued and signed by another + * + * @param crt Certificate to check the signature of + * @param issuer Issuer's certificate + * + * @return TRUE if crt was signed and issued by issuer, otherwise FALSE + * @TODO Modify this function to return a reason for invalidity? + */ +static gboolean +x509_certificate_signed_by(PurpleCertificate * crt, + PurpleCertificate * issuer) +{ + gnutls_x509_crt_t crt_dat; + gnutls_x509_crt_t issuer_dat; + unsigned int verify; /* used to store result from GnuTLS verifier */ + int ret; + + g_return_val_if_fail(crt, FALSE); + g_return_val_if_fail(issuer, FALSE); + + /* Verify that both certs are the correct scheme */ + g_return_val_if_fail(crt->scheme == &x509_gnutls, FALSE); + g_return_val_if_fail(issuer->scheme == &x509_gnutls, FALSE); + + /* TODO: check for more nullness? */ + + crt_dat = X509_GET_GNUTLS_DATA(crt); + issuer_dat = X509_GET_GNUTLS_DATA(issuer); + + /* First, let's check that crt.issuer is actually issuer */ + ret = gnutls_x509_crt_check_issuer(crt_dat, issuer_dat); + if (ret <= 0) { + + if (ret < 0) { + purple_debug_error("gnutls/x509", + "GnuTLS error %d while checking certificate issuer match.", + ret); + } else { + gchar *crt_id, *issuer_id, *crt_issuer_id; + crt_id = purple_certificate_get_unique_id(crt); + issuer_id = purple_certificate_get_unique_id(issuer); + 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. A strcmp " + "says %d\n", + crt_id, crt_issuer_id, issuer_id, + strcmp(crt_issuer_id, issuer_id)); + g_free(crt_id); + g_free(issuer_id); + g_free(crt_issuer_id); + } + + /* The issuer is not correct, or there were errors */ + return FALSE; + } + + /* Now, check the signature */ + /* The second argument is a ptr to an array of "trusted" issuer certs, + but we're only using one trusted one */ + ret = gnutls_x509_crt_verify(crt_dat, &issuer_dat, 1, + /* Permit signings by X.509v1 certs + (Verisign and possibly others have + root certificates that predate the + current standard) */ + GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT, + &verify); + + if (ret != 0) { + purple_debug_error("gnutls/x509", + "Attempted certificate verification caused a GnuTLS error code %d. I will just say the signature is bad, but you should look into this.\n", ret); + return FALSE; + } + + if (verify & GNUTLS_CERT_INVALID) { + /* Signature didn't check out, but at least + there were no errors*/ + gchar *crt_id = purple_certificate_get_unique_id(crt); + gchar *issuer_id = purple_certificate_get_issuer_unique_id(crt); + purple_debug_info("gnutls/x509", + "Bad signature for %s on %s\n", + issuer_id, crt_id); + g_free(crt_id); + g_free(issuer_id); + + return FALSE; + } /* if (ret, etc.) */ + + /* If we got here, the signature is good */ + return TRUE; +} + +static GByteArray * +x509_sha1sum(PurpleCertificate *crt) +{ + size_t hashlen = 20; /* SHA1 hashes are 20 bytes */ + size_t tmpsz = hashlen; /* Throw-away variable for GnuTLS to stomp on*/ + gnutls_x509_crt_t crt_dat; + GByteArray *hash; /**< Final hash container */ + guchar hashbuf[hashlen]; /**< Temporary buffer to contain hash */ + + g_return_val_if_fail(crt, NULL); + + crt_dat = X509_GET_GNUTLS_DATA(crt); + + /* Extract the fingerprint */ + g_return_val_if_fail( + 0 == gnutls_x509_crt_get_fingerprint(crt_dat, GNUTLS_MAC_SHA, + hashbuf, &tmpsz), + NULL); + + /* This shouldn't happen */ + g_return_val_if_fail(tmpsz == hashlen, NULL); + + /* Okay, now create and fill hash array */ + hash = g_byte_array_new(); + g_byte_array_append(hash, hashbuf, hashlen); + + return hash; +} + +static gchar * +x509_cert_dn (PurpleCertificate *crt) +{ + gnutls_x509_crt_t cert_dat; + gchar *dn = NULL; + size_t dn_size; + + g_return_val_if_fail(crt, NULL); + g_return_val_if_fail(crt->scheme == &x509_gnutls, NULL); + + cert_dat = X509_GET_GNUTLS_DATA(crt); + + /* Figure out the length of the Distinguished Name */ + /* Claim that the buffer is size 0 so GnuTLS just tells us how much + space it needs */ + dn_size = 0; + gnutls_x509_crt_get_dn(cert_dat, dn, &dn_size); + + /* Now allocate and get the Distinguished Name */ + dn = g_new0(gchar, dn_size); + if (0 != gnutls_x509_crt_get_dn(cert_dat, dn, &dn_size)) { + purple_debug_error("gnutls/x509", + "Failed to get Distinguished Name\n"); + g_free(dn); + return NULL; + } + + return dn; +} + +static gchar * +x509_issuer_dn (PurpleCertificate *crt) +{ + gnutls_x509_crt_t cert_dat; + gchar *dn = NULL; + size_t dn_size; + + g_return_val_if_fail(crt, NULL); + g_return_val_if_fail(crt->scheme == &x509_gnutls, NULL); + + cert_dat = X509_GET_GNUTLS_DATA(crt); + + /* Figure out the length of the Distinguished Name */ + /* Claim that the buffer is size 0 so GnuTLS just tells us how much + space it needs */ + dn_size = 0; + gnutls_x509_crt_get_issuer_dn(cert_dat, dn, &dn_size); + + /* Now allocate and get the Distinguished Name */ + dn = g_new0(gchar, dn_size); + if (0 != gnutls_x509_crt_get_issuer_dn(cert_dat, dn, &dn_size)) { + purple_debug_error("gnutls/x509", + "Failed to get issuer's Distinguished " + "Name\n"); + g_free(dn); + return NULL; + } + + return dn; +} + +static gchar * +x509_common_name (PurpleCertificate *crt) +{ + gnutls_x509_crt_t cert_dat; + gchar *cn = NULL; + size_t cn_size; + int ret; + + g_return_val_if_fail(crt, NULL); + g_return_val_if_fail(crt->scheme == &x509_gnutls, NULL); + + cert_dat = X509_GET_GNUTLS_DATA(crt); + + /* Figure out the length of the Common Name */ + /* Claim that the buffer is size 0 so GnuTLS just tells us how much + space it needs */ + cn_size = 0; + gnutls_x509_crt_get_dn_by_oid(cert_dat, + GNUTLS_OID_X520_COMMON_NAME, + 0, /* First CN found, please */ + 0, /* Not in raw mode */ + cn, &cn_size); + + /* Now allocate and get the Common Name */ + cn = g_new0(gchar, cn_size); + ret = gnutls_x509_crt_get_dn_by_oid(cert_dat, + GNUTLS_OID_X520_COMMON_NAME, + 0, /* First CN found, please */ + 0, /* Not in raw mode */ + cn, &cn_size); + if (ret != 0) { + purple_debug_error("gnutls/x509", + "Failed to get Common Name\n"); + g_free(cn); + return NULL; + } + + return cn; +} + +static gboolean +x509_check_name (PurpleCertificate *crt, const gchar *name) +{ + gnutls_x509_crt_t crt_dat; + + g_return_val_if_fail(crt, FALSE); + g_return_val_if_fail(crt->scheme == &x509_gnutls, FALSE); + g_return_val_if_fail(name, FALSE); + + crt_dat = X509_GET_GNUTLS_DATA(crt); + + if (gnutls_x509_crt_check_hostname(crt_dat, name)) { + return TRUE; + } else { + return FALSE; + } +} + +static gboolean +x509_times (PurpleCertificate *crt, time_t *activation, time_t *expiration) +{ + gnutls_x509_crt_t crt_dat; + /* GnuTLS time functions return this on error */ + const time_t errval = (time_t) (-1); + + + g_return_val_if_fail(crt, FALSE); + g_return_val_if_fail(crt->scheme == &x509_gnutls, FALSE); + + crt_dat = X509_GET_GNUTLS_DATA(crt); + + if (activation) { + *activation = gnutls_x509_crt_get_activation_time(crt_dat); + } + if (expiration) { + *expiration = gnutls_x509_crt_get_expiration_time(crt_dat); + } + + if (*activation == errval || *expiration == errval) { + return FALSE; + } + + return TRUE; +} + +/* X.509 certificate operations provided by this plugin */ +static PurpleCertificateScheme x509_gnutls = { + "x509", /* Scheme name */ + N_("X.509 Certificates"), /* User-visible scheme name */ + x509_import_from_file, /* Certificate import function */ + x509_export_certificate, /* Certificate export function */ + x509_copy_certificate, /* Copy */ + x509_destroy_certificate, /* Destroy cert */ + x509_certificate_signed_by, /* Signature checker */ + x509_sha1sum, /* SHA1 fingerprint */ + x509_cert_dn, /* Unique ID */ + x509_issuer_dn, /* Issuer Unique ID */ + x509_common_name, /* Subject name */ + x509_check_name, /* Check subject name */ + x509_times, /* Activation/Expiration time */ + + NULL, + NULL, + NULL, + NULL + +}; + static PurpleSslOps ssl_ops = { ssl_gnutls_init, @@ -221,11 +928,11 @@ ssl_gnutls_close, ssl_gnutls_read, ssl_gnutls_write, + ssl_gnutls_get_peer_certificates, /* padding */ NULL, NULL, - NULL, NULL }; @@ -242,6 +949,9 @@ /* Init GNUTLS now so others can use it even if sslconn never does */ ssl_gnutls_init_gnutls(); + /* Register that we're providing an X.509 CertScheme */ + purple_certificate_register_scheme( &x509_gnutls ); + return TRUE; #else return FALSE; @@ -255,6 +965,8 @@ if(purple_ssl_get_ops() == &ssl_ops) { purple_ssl_set_ops(NULL); } + + purple_certificate_unregister_scheme( &x509_gnutls ); #endif return TRUE;
--- a/libpurple/plugins/ssl/ssl-nss.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/plugins/ssl/ssl-nss.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,10 +17,11 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "internal.h" #include "debug.h" +#include "certificate.h" #include "plugin.h" #include "sslconn.h" #include "version.h" @@ -106,6 +107,20 @@ } } +static gchar *get_error_text() +{ + PRInt32 len = PR_GetErrorTextLength(); + gchar *ret = NULL; + + if (len > 0) { + ret = g_malloc(len + 1); + len = PR_GetErrorText(ret); + ret[len] = '\0'; + } + + return ret; +} + static void ssl_nss_init_nss(void) { @@ -219,11 +234,14 @@ * It seems to work because it'll eventually use the cached value */ if(SSL_ForceHandshake(nss_data->in) != SECSuccess) { + gchar *error_txt; set_errno(PR_GetError()); if (errno == EAGAIN || errno == EWOULDBLOCK) return; - purple_debug_error("nss", "Handshake failed %d\n", PR_GetError()); + error_txt = get_error_text(); + purple_debug_error("nss", "Handshake failed %s (%d)\n", error_txt ? error_txt : "", PR_GetError()); + g_free(error_txt); if (gsc->error_cb != NULL) gsc->error_cb(gsc, PURPLE_SSL_HANDSHAKE_FAILED, gsc->connect_cb_data); @@ -264,8 +282,11 @@ socket_opt.option = PR_SockOpt_Nonblocking; socket_opt.value.non_blocking = PR_TRUE; - if (PR_SetSocketOption(nss_data->fd, &socket_opt) != PR_SUCCESS) - purple_debug_warning("nss", "unable to set socket into non-blocking mode: %d\n", PR_GetError()); + if (PR_SetSocketOption(nss_data->fd, &socket_opt) != PR_SUCCESS) { + gchar *error_txt = get_error_text(); + purple_debug_warning("nss", "unable to set socket into non-blocking mode: %s (%d)\n", error_txt ? error_txt : "", PR_GetError()); + g_free(error_txt); + } nss_data->in = SSL_ImportFD(NULL, nss_data->fd); @@ -360,6 +381,298 @@ return ret; } +static GList * +ssl_nss_peer_certs(PurpleSslConnection *gsc) +{ + PurpleSslNssData *nss_data = PURPLE_SSL_NSS_DATA(gsc); + CERTCertificate *cert; +/* + GList *chain = NULL; + void *pinArg; + SECStatus status; +*/ + + /* TODO: this is a blind guess */ + cert = SSL_PeerCertificate(nss_data->fd); + + + + return NULL; +} + +/************************************************************************/ +/* X.509 functionality */ +/************************************************************************/ +static PurpleCertificateScheme x509_nss; + +/** Helpr macro to retrieve the NSS certdata from a PurpleCertificate */ +#define X509_NSS_DATA(pcrt) ( (CERTCertificate * ) (pcrt->data) ) + +/** Imports a PEM-formatted X.509 certificate from the specified file. + * @param filename Filename to import from. Format is PEM + * + * @return A newly allocated Certificate structure of the x509_gnutls scheme + */ +static PurpleCertificate * +x509_import_from_file(const gchar *filename) +{ + gchar *rawcert; + gsize len = 0; + CERTCertificate *crt_dat; + PurpleCertificate *crt; + + g_return_val_if_fail(filename, NULL); + + purple_debug_info("nss/x509", + "Loading certificate from %s\n", + filename); + + /* Load the raw data up */ + g_return_val_if_fail( + g_file_get_contents(filename, + &rawcert, &len, + NULL ), + NULL); + + /* Decode the certificate */ + crt_dat = CERT_DecodeCertFromPackage(rawcert, len); + g_free(rawcert); + + g_return_val_if_fail(crt_dat, NULL); + + crt = g_new0(PurpleCertificate, 1); + crt->scheme = &x509_nss; + crt->data = crt_dat; + + return crt; +} + +/** + * Exports a PEM-formatted X.509 certificate to the specified file. + * @param filename Filename to export to. Format will be PEM + * @param crt Certificate to export + * + * @return TRUE if success, otherwise FALSE + */ +static gboolean +x509_export_certificate(const gchar *filename, PurpleCertificate *crt) +{ + /* TODO: WRITEME */ + return FALSE; +} + +static PurpleCertificate * +x509_copy_certificate(PurpleCertificate *crt) +{ + CERTCertificate *crt_dat; + PurpleCertificate *newcrt; + + g_return_val_if_fail(crt, NULL); + g_return_val_if_fail(crt->scheme == &x509_nss, NULL); + + crt_dat = X509_NSS_DATA(crt); + g_return_val_if_fail(crt_dat, NULL); + + /* Create the certificate copy */ + newcrt = g_new0(PurpleCertificate, 1); + newcrt->scheme = &x509_nss; + /* NSS does refcounting automatically */ + newcrt->data = CERT_DupCertificate(crt_dat); + + return newcrt; +} + +/** Frees a Certificate + * + * Destroys a Certificate's internal data structures and frees the pointer + * given. + * @param crt Certificate instance to be destroyed. It WILL NOT be destroyed + * if it is not of the correct CertificateScheme. Can be NULL + * + */ +static void +x509_destroy_certificate(PurpleCertificate * crt) +{ + CERTCertificate *crt_dat; + + g_return_if_fail(crt); + g_return_if_fail(crt->scheme == &x509_nss); + + crt_dat = X509_NSS_DATA(crt); + g_return_if_fail(crt_dat); + + /* Finally we have the certificate. So let's kill it */ + /* NSS does refcounting automatically */ + CERT_DestroyCertificate(crt_dat); + + /* Delete the PurpleCertificate as well */ + g_free(crt); +} + +#if 0 +/** Determines whether one certificate has been issued and signed by another + * + * @param crt Certificate to check the signature of + * @param issuer Issuer's certificate + * + * @return TRUE if crt was signed and issued by issuer, otherwise FALSE + * @TODO Modify this function to return a reason for invalidity? + */ +static gboolean +x509_certificate_signed_by(PurpleCertificate * crt, + PurpleCertificate * issuer) +{ + return FALSE; +} +#endif + +static GByteArray * +x509_sha1sum(PurpleCertificate *crt) +{ + CERTCertificate *crt_dat; + size_t hashlen = 20; /* Size of an sha1sum */ + GByteArray *sha1sum; + SECItem *derCert; /* DER representation of the cert */ + SECStatus st; + + g_return_val_if_fail(crt, NULL); + g_return_val_if_fail(crt->scheme == &x509_nss, NULL); + + crt_dat = X509_NSS_DATA(crt); + g_return_val_if_fail(crt_dat, NULL); + + /* Get the certificate DER representation */ + derCert = &(crt_dat->derCert); + + /* Make a hash! */ + sha1sum = g_byte_array_sized_new(hashlen); + /* glib leaves the size as 0 by default */ + sha1sum->len = hashlen; + + st = PK11_HashBuf(SEC_OID_SHA1, sha1sum->data, + derCert->data, derCert->len); + + /* Check for errors */ + if (st != SECSuccess) { + g_byte_array_free(sha1sum, TRUE); + purple_debug_error("nss/x509", + "Error: hashing failed!\n"); + return NULL; + } + + return sha1sum; +} + +static gchar * +x509_common_name (PurpleCertificate *crt) +{ + CERTCertificate *crt_dat; + char *nss_cn; + gchar *ret_cn; + + g_return_val_if_fail(crt, NULL); + g_return_val_if_fail(crt->scheme == &x509_nss, NULL); + + crt_dat = X509_NSS_DATA(crt); + g_return_val_if_fail(crt_dat, NULL); + + /* Q: + Why get a newly allocated string out of NSS, strdup it, and then + return the new copy? + + A: + The NSS LXR docs state that I should use the NSPR free functions on + the strings that the NSS cert functions return. Since the libpurple + API expects a g_free()-able string, we make our own copy and return + that. + + NSPR is something of a prima donna. */ + + nss_cn = CERT_GetCommonName( &(crt_dat->subject) ); + ret_cn = g_strdup(nss_cn); + PORT_Free(nss_cn); + + return ret_cn; +} + +static gboolean +x509_check_name (PurpleCertificate *crt, const gchar *name) +{ + CERTCertificate *crt_dat; + SECStatus st; + + g_return_val_if_fail(crt, FALSE); + g_return_val_if_fail(crt->scheme == &x509_nss, FALSE); + + crt_dat = X509_NSS_DATA(crt); + g_return_val_if_fail(crt_dat, FALSE); + + st = CERT_VerifyCertName(crt_dat, name); + + if (st == SECSuccess) { + return TRUE; + } + else if (st == SECFailure) { + return FALSE; + } + + /* If we get here...bad things! */ + purple_debug_error("nss/x509", + "x509_check_name fell through where it shouldn't " + "have.\n"); + return FALSE; +} + +static gboolean +x509_times (PurpleCertificate *crt, time_t *activation, time_t *expiration) +{ + CERTCertificate *crt_dat; + PRTime nss_activ, nss_expir; + + g_return_val_if_fail(crt, FALSE); + g_return_val_if_fail(crt->scheme == &x509_nss, FALSE); + + crt_dat = X509_NSS_DATA(crt); + g_return_val_if_fail(crt_dat, FALSE); + + /* Extract the times into ugly PRTime thingies */ + /* TODO: Maybe this shouldn't throw an error? */ + g_return_val_if_fail( + SECSuccess == CERT_GetCertTimes(crt_dat, + &nss_activ, &nss_expir), + FALSE); + + if (activation) { + *activation = nss_activ; + } + if (expiration) { + *expiration = nss_expir; + } + + return TRUE; +} + +static PurpleCertificateScheme x509_nss = { + "x509", /* Scheme name */ + N_("X.509 Certificates"), /* User-visible scheme name */ + x509_import_from_file, /* Certificate import function */ + x509_export_certificate, /* Certificate export function */ + x509_copy_certificate, /* Copy */ + x509_destroy_certificate, /* Destroy cert */ + NULL, /* Signed-by */ + x509_sha1sum, /* SHA1 fingerprint */ + NULL, /* Unique ID */ + NULL, /* Issuer Unique ID */ + x509_common_name, /* Subject name */ + x509_check_name, /* Check subject name */ + x509_times, /* Activation/Expiration time */ + + NULL, + NULL, + NULL, + NULL +}; + static PurpleSslOps ssl_ops = { ssl_nss_init, @@ -368,11 +681,11 @@ ssl_nss_close, ssl_nss_read, ssl_nss_write, + ssl_nss_peer_certs, /* padding */ NULL, NULL, - NULL, NULL }; @@ -390,6 +703,9 @@ /* Init NSS now, so others can use it even if sslconn never does */ ssl_nss_init_nss(); + /* Register the X.509 functions we provide */ + purple_certificate_register_scheme(&x509_nss); + return TRUE; #else return FALSE; @@ -403,6 +719,9 @@ if (purple_ssl_get_ops() == &ssl_ops) { purple_ssl_set_ops(NULL); } + + /* Unregister our X.509 functions */ + purple_certificate_unregister_scheme(&x509_nss); #endif return TRUE;
--- a/libpurple/plugins/ssl/ssl.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/plugins/ssl/ssl.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "internal.h" #include "debug.h"
--- a/libpurple/plugins/startup.py Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/plugins/startup.py Wed Sep 12 19:11:38 2007 +0000 @@ -18,7 +18,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA # import sys
--- a/libpurple/plugins/tcl/tcl.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/plugins/tcl/tcl.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "config.h"
--- a/libpurple/plugins/tcl/tcl_cmd.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/plugins/tcl/tcl_cmd.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include <tcl.h>
--- a/libpurple/plugins/tcl/tcl_cmds.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/plugins/tcl/tcl_cmds.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include <tcl.h> @@ -544,12 +544,16 @@ int tcl_cmd_cmd(ClientData unused, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) { - const char *cmds[] = { "register", "unregister", NULL }; - enum { CMD_CMD_REGISTER, CMD_CMD_UNREGISTER } cmd; + const char *cmds[] = { "do", "help", "list", "register", "unregister", NULL }; + enum { CMD_CMD_DO, CMD_CMD_HELP, CMD_CMD_LIST, CMD_CMD_REGISTER, CMD_CMD_UNREGISTER } cmd; struct tcl_cmd_handler *handler; - Tcl_Obj *result = Tcl_GetObjResult(interp); + Tcl_Obj *list, *elem, *result = Tcl_GetObjResult(interp); + PurpleConversation *convo; PurpleCmdId id; + PurpleCmdStatus status; int error; + GList *l, *cur; + gchar *escaped, *errstr = NULL; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?args?"); @@ -560,6 +564,57 @@ return error; switch (cmd) { + case CMD_CMD_DO: + if (objc != 4) { + Tcl_WrongNumArgs(interp, 2, objv, "conversation command"); + return TCL_ERROR; + } + if ((convo = tcl_validate_conversation(objv[2], interp)) == NULL) + return TCL_ERROR; + escaped = g_markup_escape_text(Tcl_GetString(objv[3]), -1); + status = purple_cmd_do_command(convo, Tcl_GetString(objv[3]), + escaped, &errstr); + g_free(escaped); + Tcl_SetStringObj(result, errstr ? (char *)errstr : "", -1); + g_free(errstr); + if (status != PURPLE_CMD_STATUS_OK) { + return TCL_ERROR; + } + break; + case CMD_CMD_HELP: + if (objc != 4) { + Tcl_WrongNumArgs(interp, 2, objv, "conversation name"); + return TCL_ERROR; + } + if ((convo = tcl_validate_conversation(objv[2], interp)) == NULL) + return TCL_ERROR; + l = cur = purple_cmd_help(convo, Tcl_GetString(objv[3])); + list = Tcl_NewListObj(0, NULL); + while (cur != NULL) { + elem = Tcl_NewStringObj((char *)cur->data, -1); + Tcl_ListObjAppendElement(interp, list, elem); + cur = g_list_next(cur); + } + g_list_free(l); + Tcl_SetObjResult(interp, list); + break; + case CMD_CMD_LIST: + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "conversation"); + return TCL_ERROR; + } + if ((convo = tcl_validate_conversation(objv[2], interp)) == NULL) + return TCL_ERROR; + l = cur = purple_cmd_list(convo); + list = Tcl_NewListObj(0, NULL); + while (cur != NULL) { + elem = Tcl_NewStringObj((char *)cur->data, -1); + Tcl_ListObjAppendElement(interp, list, elem); + cur = g_list_next(cur); + } + g_list_free(l); + Tcl_SetObjResult(interp, list); + break; case CMD_CMD_REGISTER: if (objc != 9) { Tcl_WrongNumArgs(interp, 2, objv, "cmd arglist priority flags prpl_id proc helpstr");
--- a/libpurple/plugins/tcl/tcl_glib.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/plugins/tcl/tcl_glib.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * * TERMS 2 *
--- a/libpurple/plugins/tcl/tcl_glib.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/plugins/tcl/tcl_glib.h Wed Sep 12 19:11:38 2007 +0000 @@ -15,7 +15,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _PURPLE_TCL_GLIB_H_
--- a/libpurple/plugins/tcl/tcl_purple.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/plugins/tcl/tcl_purple.h Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _PURPLE_TCL_PURPLE_H_
--- a/libpurple/plugins/tcl/tcl_ref.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/plugins/tcl/tcl_ref.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include <tcl.h>
--- a/libpurple/plugins/tcl/tcl_signals.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/plugins/tcl/tcl_signals.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include <tcl.h> #include <stdarg.h>
--- a/libpurple/pounce.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/pounce.c Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "internal.h" #include "conversation.h"
--- a/libpurple/pounce.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/pounce.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _PURPLE_POUNCE_H_ #define _PURPLE_POUNCE_H_
--- a/libpurple/prefs.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/prefs.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */ @@ -329,9 +329,13 @@ purple_prefs_set_string_list(pref_name_full->str, NULL); break; case PURPLE_PREF_PATH: - decoded = g_filename_from_utf8(pref_value, -1, NULL, NULL, NULL); - purple_prefs_set_path(pref_name_full->str, decoded); - g_free(decoded); + if (pref_value) { + decoded = g_filename_from_utf8(pref_value, -1, NULL, NULL, NULL); + purple_prefs_set_path(pref_name_full->str, decoded); + g_free(decoded); + } else { + purple_prefs_set_path(pref_name_full->str, NULL); + } break; case PURPLE_PREF_PATH_LIST: purple_prefs_set_path_list(pref_name_full->str, NULL); @@ -1446,6 +1450,9 @@ purple_prefs_remove("/purple/contact/offline_score"); purple_prefs_remove("/purple/contact/away_score"); purple_prefs_remove("/purple/contact/idle_score"); + + purple_prefs_load(); + purple_prefs_update_old(); } void
--- a/libpurple/prefs.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/prefs.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */ #ifndef _PURPLE_PREFS_H_ @@ -55,7 +55,9 @@ #endif /**************************************************************************/ -/** @name Prefs API */ +/** @name Prefs API + Preferences are named according to a directory-like structure. + Example: "/plugins/core/potato/is_from_idaho" (probably a boolean) */ /**************************************************************************/ /*@{*/
--- a/libpurple/privacy.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/privacy.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "internal.h"
--- a/libpurple/privacy.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/privacy.h Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _PURPLE_PRIVACY_H_ #define _PURPLE_PRIVACY_H_
--- a/libpurple/protocols/Makefile.am Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/Makefile.am Wed Sep 12 19:11:38 2007 +0000 @@ -1,5 +1,5 @@ EXTRA_DIST = Makefile.mingw -DIST_SUBDIRS = bonjour gg irc jabber msn novell null oscar qq sametime silc silc10 toc simple yahoo zephyr +DIST_SUBDIRS = bonjour gg irc jabber msn myspace novell null oscar qq sametime silc silc10 toc simple yahoo zephyr SUBDIRS = $(DYNAMIC_PRPLS) $(STATIC_PRPLS)
--- a/libpurple/protocols/Makefile.mingw Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/Makefile.mingw Wed Sep 12 19:11:38 2007 +0000 @@ -8,7 +8,7 @@ PIDGIN_TREE_TOP := ../.. include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak -SUBDIRS = gg irc jabber msn novell null oscar qq sametime silc10 simple yahoo bonjour +SUBDIRS = gg irc jabber msn novell null oscar qq sametime silc simple yahoo bonjour myspace .PHONY: all install clean
--- a/libpurple/protocols/bonjour/bonjour.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/bonjour/bonjour.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include <glib.h> #ifndef _WIN32 @@ -235,6 +235,13 @@ g_free(stripped); } +static void bonjour_remove_buddy(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group) { + if (buddy->proto_data) { + bonjour_buddy_delete(buddy->proto_data); + buddy->proto_data = NULL; + } +} + static GList * bonjour_status_types(PurpleAccount *account) { @@ -395,7 +402,7 @@ NULL, /* change_passwd */ NULL, /* add_buddy */ NULL, /* add_buddies */ - NULL, /* remove_buddy */ + bonjour_remove_buddy, /* remove_buddy */ NULL, /* remove_buddies */ NULL, /* add_permit */ NULL, /* add_deny */ @@ -479,45 +486,50 @@ NULL }; -static void -initialize_default_account_values() -{ -#ifdef _WIN32 - char *fullname = NULL; -#else - struct passwd *info; - const char *fullname = NULL; -#endif - char *splitpoint = NULL; - char *tmp; - char hostname[255]; +#ifdef WIN32 +static gboolean _set_default_name_cb(gpointer data) { + gchar *fullname = data; + const char *splitpoint; + GList *tmp = prpl_info.protocol_options; + PurpleAccountOption *option; -#ifndef _WIN32 - /* Try to figure out the user's real name */ - info = getpwuid(getuid()); - if ((info != NULL) && (info->pw_gecos != NULL) && (info->pw_gecos[0] != '\0')) - fullname = info->pw_gecos; - else if ((info != NULL) && (info->pw_name != NULL) && (info->pw_name[0] != '\0')) - fullname = info->pw_name; - else if (((fullname = getlogin()) != NULL) && (fullname[0] != '\0')) - ; - else - fullname = _("Purple Person"); - /* Make sure fullname is valid UTF-8. If not, try to convert it. */ - if (!g_utf8_validate(fullname, -1, NULL)) - { - gchar *tmp; - tmp = g_locale_to_utf8(fullname, -1, NULL, NULL, NULL); - if ((tmp == NULL) || (*tmp == '\0')) - fullname = _("Purple Person"); + if (!fullname) { + purple_debug_info("bonjour", "Unable to look up First and Last name or Username from system; using defaults.\n"); + return FALSE; } -#else + g_free(default_firstname); + g_free(default_lastname); + + /* Split the real name into a first and last name */ + splitpoint = strchr(fullname, ' '); + if (splitpoint != NULL) { + default_firstname = g_strndup(fullname, splitpoint - fullname); + default_lastname = g_strdup(&splitpoint[1]); + } else { + default_firstname = g_strdup(fullname); + default_lastname = g_strdup(""); + } + g_free(fullname); + + + for(; tmp != NULL; tmp = tmp->next) { + option = tmp->data; + if (strcmp("first", purple_account_option_get_setting(option)) == 0) + purple_account_option_set_default_string(option, default_firstname); + else if (strcmp("last", purple_account_option_get_setting(option)) == 0) + purple_account_option_set_default_string(option, default_lastname); + } + + return FALSE; +} + +static gpointer _win32_name_lookup_thread(gpointer data) { + gchar *fullname = NULL; wchar_t username[UNLEN + 1]; DWORD dwLenUsername = UNLEN + 1; - if (!GetUserNameW((LPWSTR) &username, &dwLenUsername)) - purple_debug_warning("bonjour", "Unable to look up username\n"); + GetUserNameW((LPWSTR) &username, &dwLenUsername); if (username != NULL && *username != '\0') { LPBYTE servername = NULL; @@ -525,7 +537,7 @@ NetGetDCName(NULL, NULL, &servername); - purple_debug_info("bonjour", "Looking up the full name from the %s.\n", (servername ? "domain controller" : "local machine")); + /* purple_debug_info("bonjour", "Looking up the full name from the %s.\n", (servername ? "domain controller" : "local machine")); */ if (NetUserGetInfo((LPCWSTR) servername, username, 10, &info) == NERR_Success && info != NULL && ((LPUSER_INFO_10) info)->usri10_full_name != NULL @@ -536,7 +548,7 @@ } /* Fall back to the local machine if we didn't get the full name from the domain controller */ else if (servername != NULL) { - purple_debug_info("bonjour", "Looking up the full name from the local machine"); + /* purple_debug_info("bonjour", "Looking up the full name from the local machine"); */ if (info != NULL) NetApiBufferFree(info); info = NULL; @@ -552,20 +564,54 @@ if (info != NULL) NetApiBufferFree(info); if (servername != NULL) NetApiBufferFree(servername); + + if (!fullname) + fullname = g_utf16_to_utf8(username, -1, NULL, NULL, NULL); } - if (!fullname) { - if (username != NULL && *username != '\0') - fullname = g_utf16_to_utf8(username, -1, NULL, NULL, NULL); - else - fullname = g_strdup(_("Purple Person")); + g_idle_add(_set_default_name_cb, fullname); + + return NULL; +} +#endif + +static void +initialize_default_account_values() +{ +#ifndef _WIN32 + struct passwd *info; +#endif + const char *fullname = NULL, *splitpoint, *tmp; + char hostname[255]; + gchar *conv = NULL; + +#ifndef _WIN32 + /* Try to figure out the user's real name */ + info = getpwuid(getuid()); + if ((info != NULL) && (info->pw_gecos != NULL) && (info->pw_gecos[0] != '\0')) + fullname = info->pw_gecos; + else if ((info != NULL) && (info->pw_name != NULL) && (info->pw_name[0] != '\0')) + fullname = info->pw_name; + else if (((fullname = getlogin()) != NULL) && (fullname[0] == '\0')) + fullname = NULL; +#else + /* The Win32 username lookup functions are synchronous so we do it in a thread */ + g_thread_create(_win32_name_lookup_thread, NULL, FALSE, NULL); +#endif + + /* Make sure fullname is valid UTF-8. If not, try to convert it. */ + if (fullname != NULL && !g_utf8_validate(fullname, -1, NULL)) { + fullname = conv = g_locale_to_utf8(fullname, -1, NULL, NULL, NULL); + if (conv == NULL || *conv == '\0') + fullname = NULL; } -#endif + + if (fullname == NULL) + fullname = _("Purple Person"); /* Split the real name into a first and last name */ splitpoint = strchr(fullname, ' '); - if (splitpoint != NULL) - { + if (splitpoint != NULL) { default_firstname = g_strndup(fullname, splitpoint - fullname); tmp = &splitpoint[1]; @@ -577,16 +623,12 @@ default_lastname = g_strndup(tmp, splitpoint - tmp); else default_lastname = g_strdup(tmp); - } - else - { + } else { default_firstname = g_strdup(fullname); default_lastname = g_strdup(""); } -#ifdef _WIN32 - g_free(fullname); -#endif + g_free(conv); /* Try to figure out a good host name to use */ /* TODO: Avoid 'localhost,' if possible */
--- a/libpurple/protocols/bonjour/bonjour.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/bonjour/bonjour.h Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */
--- a/libpurple/protocols/bonjour/buddy.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/bonjour/buddy.c Wed Sep 12 19:11:38 2007 +0000 @@ -11,7 +11,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA. */ #include <glib.h> @@ -121,9 +121,8 @@ * the buddy. */ void -bonjour_buddy_add_to_purple(BonjourBuddy *bonjour_buddy) +bonjour_buddy_add_to_purple(BonjourBuddy *bonjour_buddy, PurpleBuddy *buddy) { - PurpleBuddy *buddy; PurpleGroup *group; PurpleAccount *account = bonjour_buddy->account; const char *status_id, *old_hash, *new_hash; @@ -147,7 +146,8 @@ } /* Make sure the buddy exists in our buddy list */ - buddy = purple_find_buddy(account, bonjour_buddy->name); + if (buddy == NULL) + buddy = purple_find_buddy(account, bonjour_buddy->name); if (buddy == NULL) { buddy = purple_buddy_new(account, bonjour_buddy->name, NULL);
--- a/libpurple/protocols/bonjour/buddy.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/bonjour/buddy.h Wed Sep 12 19:11:38 2007 +0000 @@ -11,7 +11,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA. */ #ifndef _BONJOUR_BUDDY @@ -92,8 +92,9 @@ /** * If the buddy doesn't previoulsy exists, it is created. Else, its data is changed (???) + * purple_buddy is optional; it saves an additional lookup if we already have it */ -void bonjour_buddy_add_to_purple(BonjourBuddy *buddy); +void bonjour_buddy_add_to_purple(BonjourBuddy *bonjour_buddy, PurpleBuddy *purple_buddy); /** * We got the buddy icon data; deal with it
--- a/libpurple/protocols/bonjour/issues.txt Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/bonjour/issues.txt Wed Sep 12 19:11:38 2007 +0000 @@ -2,6 +2,5 @@ ============= Known issues =============== ========================================== -* Status changes don't work * File transfers * Typing notifications
--- a/libpurple/protocols/bonjour/jabber.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/bonjour/jabber.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _WIN32 #include <sys/socket.h> @@ -82,8 +82,8 @@ BonjourJabberConversation *bconv = g_new0(BonjourJabberConversation, 1); bconv->socket = -1; bconv->tx_buf = purple_circ_buffer_new(512); - bconv->tx_handler = -1; - bconv->rx_handler = -1; + bconv->tx_handler = 0; + bconv->rx_handler = 0; return bconv; } @@ -234,7 +234,7 @@ if (writelen == 0) { purple_input_remove(bconv->tx_handler); - bconv->tx_handler = -1; + bconv->tx_handler = 0; return; } @@ -272,7 +272,7 @@ BonjourJabberConversation *bconv = bb->conversation; /* If we're not ready to actually send, append it to the buffer */ - if (bconv->tx_handler != -1 + if (bconv->tx_handler != 0 || bconv->connect_data != NULL || !bconv->sent_stream_start || !bconv->recv_stream_start @@ -304,7 +304,7 @@ } if (ret < len) { - if (bconv->tx_handler == -1) + if (bconv->tx_handler == 0) bconv->tx_handler = purple_input_add(bconv->socket, PURPLE_INPUT_WRITE, _send_data_write_cb, pb); purple_circ_buffer_append(bconv->tx_buf, message + ret, len - ret); @@ -373,17 +373,19 @@ g_return_if_fail(bb != NULL); - /* Close the socket, clear the watcher and free memory */ - bonjour_jabber_close_conversation(bb->conversation); - bb->conversation = NULL; + /* Inform the user that the conversation has been closed */ + if (bb->conversation != NULL) { + conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, pb->name, pb->account); + if (conv != NULL) { + char *tmp = g_strdup_printf(_("%s has closed the conversation."), pb->name); + purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL)); + g_free(tmp); + } + /* Close the socket, clear the watcher and free memory */ + bonjour_jabber_close_conversation(bb->conversation); + bb->conversation = NULL; + } - /* Inform the user that the conversation has been closed */ - conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, pb->name, pb->account); - if (conv != NULL) { - char *tmp = g_strdup_printf(_("%s has closed the conversation."), pb->name); - purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL)); - g_free(tmp); - } } void bonjour_jabber_stream_started(PurpleBuddy *pb) { @@ -455,7 +457,7 @@ /* Stream started; process the send buffer if there is one */ purple_input_remove(bconv->tx_handler); - bconv->tx_handler= -1; + bconv->tx_handler= 0; bconv->sent_stream_start = TRUE; bonjour_jabber_stream_started(pb); @@ -535,7 +537,7 @@ /* Look for the buddy that has opened the conversation and fill information */ address_text = inet_ntoa(their_addr.sin_addr); - purple_debug_info("bonjour", "Received incoming connection from %s\n.", address_text); + purple_debug_info("bonjour", "Received incoming connection from %s.\n", address_text); cbba = g_new0(struct _check_buddy_by_address_t, 1); cbba->address = address_text; cbba->pb = &pb; @@ -779,7 +781,7 @@ /* TODO: We're really supposed to wait for "</stream:stream>" before closing the socket */ close(bconv->socket); } - if (bconv->rx_handler != -1) + if (bconv->rx_handler != 0) purple_input_remove(bconv->rx_handler); if (bconv->tx_handler > 0) purple_input_remove(bconv->tx_handler);
--- a/libpurple/protocols/bonjour/jabber.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/bonjour/jabber.h Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */
--- a/libpurple/protocols/bonjour/mdns_avahi.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/bonjour/mdns_avahi.c Wed Sep 12 19:11:38 2007 +0000 @@ -11,7 +11,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA. */ #include "internal.h" @@ -33,7 +33,7 @@ #include <avahi-glib/glib-malloc.h> #include <avahi-glib/glib-watch.h> -/* For some reason, this is missing from the Avahi type defines */ +/* Avahi only defines the types that it actually uses (which at this time doesn't include NULL) */ #ifndef AVAHI_DNS_TYPE_NULL #define AVAHI_DNS_TYPE_NULL 0x0A #endif @@ -58,7 +58,8 @@ const char *host_name, const AvahiAddress *a, uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags flags, void *userdata) { - BonjourBuddy *buddy; + PurpleBuddy *pb; + BonjourBuddy *bb; PurpleAccount *account = userdata; AvahiStringList *l; size_t size; @@ -67,44 +68,62 @@ g_return_if_fail(r != NULL); + pb = purple_find_buddy(account, name); + bb = (pb != NULL) ? pb->proto_data : NULL; + switch (event) { case AVAHI_RESOLVER_FAILURE: purple_debug_error("bonjour", "_resolve_callback - Failure: %s\n", avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r)))); + avahi_service_resolver_free(r); + if (bb != NULL) { + /* We've already freed the resolver */ + if (r == ((AvahiBuddyImplData *)bb->mdns_impl_data)->resolver) + ((AvahiBuddyImplData *)bb->mdns_impl_data)->resolver = NULL; + purple_blist_remove_buddy(pb); + } break; case AVAHI_RESOLVER_FOUND: /* create a buddy record */ - buddy = bonjour_buddy_new(name, account); + if (bb == NULL) + bb = bonjour_buddy_new(name, account); - ((AvahiBuddyImplData *)buddy->mdns_impl_data)->resolver = r; + /* If we're reusing an existing buddy, make sure if it is a different resolver to clean up the old one. + * I don't think this should ever happen, but I'm afraid we might get events out of sequence. */ + if (((AvahiBuddyImplData *)bb->mdns_impl_data)->resolver != NULL + && ((AvahiBuddyImplData *)bb->mdns_impl_data)->resolver != r) { + avahi_service_resolver_free(((AvahiBuddyImplData *)bb->mdns_impl_data)->resolver); + } + ((AvahiBuddyImplData *)bb->mdns_impl_data)->resolver = r; + g_free(bb->ip); /* Get the ip as a string */ - buddy->ip = g_malloc(AVAHI_ADDRESS_STR_MAX); - avahi_address_snprint(buddy->ip, AVAHI_ADDRESS_STR_MAX, a); + bb->ip = g_malloc(AVAHI_ADDRESS_STR_MAX); + avahi_address_snprint(bb->ip, AVAHI_ADDRESS_STR_MAX, a); - buddy->port_p2pj = port; + bb->port_p2pj = port; /* Obtain the parameters from the text_record */ - clear_bonjour_buddy_values(buddy); - l = txt; - while (l != NULL) { - ret = avahi_string_list_get_pair(l, &key, &value, &size); - l = l->next; - if (ret < 0) + clear_bonjour_buddy_values(bb); + for(l = txt; l != NULL; l = l->next) { + if ((ret = avahi_string_list_get_pair(l, &key, &value, &size)) < 0) continue; - set_bonjour_buddy_value(buddy, key, value, size); + set_bonjour_buddy_value(bb, key, value, size); /* TODO: Since we're using the glib allocator, I think we * can use the values instead of re-copying them */ avahi_free(key); avahi_free(value); } - if (!bonjour_buddy_check(buddy)) - bonjour_buddy_delete(buddy); - else + if (!bonjour_buddy_check(bb)) { + if (pb != NULL) + purple_blist_remove_buddy(pb); + else + bonjour_buddy_delete(bb); + } else /* Add or update the buddy in our buddy list */ - bonjour_buddy_add_to_purple(buddy); + bonjour_buddy_add_to_purple(bb, pb); break; default: @@ -120,7 +139,7 @@ AvahiLookupResultFlags flags, void *userdata) { PurpleAccount *account = userdata; - PurpleBuddy *gb = NULL; + PurpleBuddy *pb = NULL; switch (event) { case AVAHI_BROWSER_FAILURE: @@ -132,7 +151,7 @@ /* A new peer has joined the network and uses iChat bonjour */ purple_debug_info("bonjour", "_browser_callback - new service\n"); /* Make sure it isn't us */ - if (g_ascii_strcasecmp(name, account->username) != 0) { + if (purple_utf8_strcasecmp(name, account->username) != 0) { if (!avahi_service_resolver_new(avahi_service_browser_get_client(b), interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, _resolver_callback, account)) { @@ -143,16 +162,12 @@ break; case AVAHI_BROWSER_REMOVE: purple_debug_info("bonjour", "_browser_callback - Remove service\n"); - gb = purple_find_buddy(account, name); - if (gb != NULL) { - bonjour_buddy_delete(gb->proto_data); - purple_blist_remove_buddy(gb); - } + pb = purple_find_buddy(account, name); + if (pb != NULL) + purple_blist_remove_buddy(pb); break; case AVAHI_BROWSER_ALL_FOR_NOW: case AVAHI_BROWSER_CACHE_EXHAUSTED: - purple_debug_warning("bonjour", "(Browser) %s\n", - event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW"); break; default: purple_debug_info("bonjour", "Unrecognized Service browser event: %d.\n", event); @@ -173,7 +188,7 @@ purple_debug_error("bonjour", "Collision registering buddy icon data.\n"); break; case AVAHI_ENTRY_GROUP_FAILURE: - purple_debug_error("bonjour", "Error registering buddy icon data: %s\n.", + purple_debug_error("bonjour", "Error registering buddy icon data: %s.\n", avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g)))); break; case AVAHI_ENTRY_GROUP_UNCOMMITED: @@ -256,7 +271,7 @@ idata->client = avahi_client_new(poll_api, 0, NULL, data, &error); if (idata->client == NULL) { - purple_debug_error("bonjour", "Error initializing Avahi: %s", avahi_strerror(error)); + purple_debug_error("bonjour", "Error initializing Avahi: %s\n", avahi_strerror(error)); avahi_glib_poll_free(idata->glib_poll); g_free(idata); return FALSE; @@ -339,7 +354,7 @@ if (!idata->sb) { purple_debug_error("bonjour", - "Unable to initialize service browser. Error: %s\n.", + "Unable to initialize service browser. Error: %s.\n", avahi_strerror(avahi_client_errno(idata->client))); return FALSE; } @@ -473,7 +488,7 @@ if (!idata->buddy_icon_rec_browser) { purple_debug_error("bonjour", - "Unable to initialize buddy icon record browser. Error: %s\n.", + "Unable to initialize buddy icon record browser. Error: %s.\n", avahi_strerror(avahi_client_errno(session_idata->client))); }
--- a/libpurple/protocols/bonjour/mdns_common.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/bonjour/mdns_common.c Wed Sep 12 19:11:38 2007 +0000 @@ -11,7 +11,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA. */ #include <string.h> @@ -207,7 +207,7 @@ /* Advise the daemon that we are waiting for connections */ if (!_mdns_browse(data)) { - purple_debug_error("bonjour", "Unable to get service."); + purple_debug_error("bonjour", "Unable to get service.\n"); return FALSE; }
--- a/libpurple/protocols/bonjour/mdns_common.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/bonjour/mdns_common.h Wed Sep 12 19:11:38 2007 +0000 @@ -11,7 +11,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA. */ #ifndef _BONJOUR_MDNS_COMMON
--- a/libpurple/protocols/bonjour/mdns_howl.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/bonjour/mdns_howl.c Wed Sep 12 19:11:38 2007 +0000 @@ -11,7 +11,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA. */ #include "internal.h" @@ -71,6 +71,7 @@ char value[SW_TEXT_RECORD_MAX_LEN]; sw_uint32 value_length; + /* TODO: We want to keep listening for updates*/ sw_discovery_cancel(discovery, oid); /* create a buddy record */ @@ -100,7 +101,7 @@ } /* Add or update the buddy in our buddy list */ - bonjour_buddy_add_to_purple(buddy); + bonjour_buddy_add_to_purple(buddy, NULL); return SW_OKAY; } @@ -149,10 +150,7 @@ purple_debug_info("bonjour", "_browser_reply --> Remove service\n"); gb = purple_find_buddy(account, name); if (gb != NULL) - { - bonjour_buddy_delete(gb->proto_data); purple_blist_remove_buddy(gb); - } break; case SW_DISCOVERY_BROWSE_RESOLVED: purple_debug_info("bonjour", "_browse_reply --> Resolved\n");
--- a/libpurple/protocols/bonjour/mdns_interface.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/bonjour/mdns_interface.h Wed Sep 12 19:11:38 2007 +0000 @@ -11,7 +11,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA. */ #ifndef _BONJOUR_MDNS_INTERFACE
--- a/libpurple/protocols/bonjour/mdns_types.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/bonjour/mdns_types.h Wed Sep 12 19:11:38 2007 +0000 @@ -11,7 +11,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA. */ #ifndef _BONJOUR_MDNS_TYPES
--- a/libpurple/protocols/bonjour/mdns_win32.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/bonjour/mdns_win32.c Wed Sep 12 19:11:38 2007 +0000 @@ -11,7 +11,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA. */ #include "internal.h" @@ -79,27 +79,27 @@ uint32_t ttl, void *context) { - if (kDNSServiceErr_NoError != errorCode) + if (kDNSServiceErr_NoError != errorCode) { purple_debug_error("bonjour", "record query - callback error.\n"); - else if (flags & kDNSServiceFlagsAdd) - { + /* TODO: Probably should remove the buddy when this happens */ + } else if (flags & kDNSServiceFlagsAdd) { if (rrtype == kDNSServiceType_TXT) { /* New Buddy */ - BonjourBuddy *buddy = (BonjourBuddy*) context; - _mdns_parse_text_record(buddy, rdata, rdlen); - bonjour_buddy_add_to_purple(buddy); + BonjourBuddy *bb = (BonjourBuddy*) context; + _mdns_parse_text_record(bb, rdata, rdlen); + bonjour_buddy_add_to_purple(bb, NULL); } else if (rrtype == kDNSServiceType_NULL) { /* Buddy Icon response */ - BonjourBuddy *buddy = (BonjourBuddy*) context; - Win32BuddyImplData *idata = buddy->mdns_impl_data; + BonjourBuddy *bb = (BonjourBuddy*) context; + Win32BuddyImplData *idata = bb->mdns_impl_data; g_return_if_fail(idata != NULL); - bonjour_buddy_got_buddy_icon(buddy, rdata, rdlen); + bonjour_buddy_got_buddy_icon(bb, rdata, rdlen); /* We've got what we need; stop listening */ purple_input_remove(idata->null_query_handler); - idata->null_query_handler = -1; + idata->null_query_handler = 0; DNSServiceRefDeallocate(idata->null_query); idata->null_query = NULL; } @@ -110,32 +110,34 @@ _mdns_resolve_host_callback(GSList *hosts, gpointer data, const char *error_message) { ResolveCallbackArgs* args = (ResolveCallbackArgs*)data; + BonjourBuddy* bb = args->buddy; - if (!hosts || !hosts->data) + if (!hosts || !hosts->data) { purple_debug_error("bonjour", "host resolution - callback error.\n"); - else { + bonjour_buddy_delete(bb); + } else { struct sockaddr_in *addr = (struct sockaddr_in*)g_slist_nth_data(hosts, 1); - BonjourBuddy* buddy = args->buddy; - Win32BuddyImplData *idata = buddy->mdns_impl_data; + Win32BuddyImplData *idata = bb->mdns_impl_data; g_return_if_fail(idata != NULL); - buddy->ip = g_strdup(inet_ntoa(addr->sin_addr)); + g_free(bb->ip); + bb->ip = g_strdup(inet_ntoa(addr->sin_addr)); /* finally, set up the continuous txt record watcher, and add the buddy to purple */ if (kDNSServiceErr_NoError == DNSServiceQueryRecord(&idata->txt_query, kDNSServiceFlagsLongLivedQuery, kDNSServiceInterfaceIndexAny, args->full_service_name, kDNSServiceType_TXT, - kDNSServiceClass_IN, _mdns_record_query_callback, buddy)) { + kDNSServiceClass_IN, _mdns_record_query_callback, bb)) { - purple_debug_info("bonjour", "Found buddy %s at %s:%d\n", buddy->name, buddy->ip, buddy->port_p2pj); + purple_debug_info("bonjour", "Found buddy %s at %s:%d\n", bb->name, bb->ip, bb->port_p2pj); idata->txt_query_handler = purple_input_add(DNSServiceRefSockFD(idata->txt_query), PURPLE_INPUT_READ, _mdns_handle_event, idata->txt_query); - bonjour_buddy_add_to_purple(buddy); + bonjour_buddy_add_to_purple(bb, NULL); } else - bonjour_buddy_delete(buddy); + bonjour_buddy_delete(bb); } @@ -202,18 +204,19 @@ DNSServiceErrorType errorCode, const char *serviceName, const char *regtype, const char *replyDomain, void *context) { PurpleAccount *account = (PurpleAccount*)context; - PurpleBuddy *gb = NULL; + PurpleBuddy *pb = NULL; if (kDNSServiceErr_NoError != errorCode) - purple_debug_error("bonjour", "service browser - callback error"); + purple_debug_error("bonjour", "service browser - callback error\n"); else if (flags & kDNSServiceFlagsAdd) { /* A presence service instance has been discovered... check it isn't us! */ - if (g_ascii_strcasecmp(serviceName, account->username) != 0) { + if (purple_utf8_strcasecmp(serviceName, account->username) != 0) { /* OK, lets go ahead and resolve it to add to the buddy list */ ResolveCallbackArgs *args = g_new0(ResolveCallbackArgs, 1); args->buddy = bonjour_buddy_new(serviceName, account); - if (kDNSServiceErr_NoError != DNSServiceResolve(&args->resolver, 0, 0, serviceName, regtype, replyDomain, _mdns_service_resolve_callback, args)) { + if (kDNSServiceErr_NoError != DNSServiceResolve(&args->resolver, 0, 0, serviceName, regtype, + replyDomain, _mdns_service_resolve_callback, args)) { bonjour_buddy_delete(args->buddy); g_free(args); purple_debug_error("bonjour", "service browser - failed to resolve service.\n"); @@ -226,11 +229,9 @@ } else { /* A peer has sent a goodbye packet, remove them from the buddy list */ purple_debug_info("bonjour", "service browser - remove notification\n"); - gb = purple_find_buddy(account, serviceName); - if (gb != NULL) { - bonjour_buddy_delete(gb->proto_data); - purple_blist_remove_buddy(gb); - } + pb = purple_find_buddy(account, serviceName); + if (pb != NULL) + purple_blist_remove_buddy(pb); } }
--- a/libpurple/protocols/bonjour/parser.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/bonjour/parser.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */ #include "internal.h"
--- a/libpurple/protocols/bonjour/parser.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/bonjour/parser.h Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _PURPLE_BONJOUR_PARSER_H_ #define _PURPLE_BONJOUR_PARSER_H_
--- a/libpurple/protocols/gg/buddylist.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/gg/buddylist.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
--- a/libpurple/protocols/gg/buddylist.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/gg/buddylist.h Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
--- a/libpurple/protocols/gg/confer.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/gg/confer.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
--- a/libpurple/protocols/gg/confer.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/gg/confer.h Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
--- a/libpurple/protocols/gg/gg-utils.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/gg/gg-utils.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
--- a/libpurple/protocols/gg/gg-utils.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/gg/gg-utils.h Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _PURPLE_GG_UTILS_H
--- a/libpurple/protocols/gg/gg.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/gg/gg.c Wed Sep 12 19:11:38 2007 +0000 @@ -22,7 +22,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "internal.h" @@ -411,6 +411,8 @@ purple_notify_info(NULL, _("New Gadu-Gadu Account Registered"), _("Registration completed successfully!"), NULL); + if(account->registration_cb) + (account->registration_cb)(account, TRUE, account->registration_cb_user_data); /* TODO: the currently open Accounts Window will not be updated withthe * new username and etc, we need to somehow have it refresh at this * point @@ -420,6 +422,9 @@ purple_connection_destroy(gc); exit_err: + if(account->registration_cb) + (account->registration_cb)(account, FALSE, account->registration_cb_user_data); + gg_register_free(h); g_free(email); g_free(p1);
--- a/libpurple/protocols/gg/gg.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/gg/gg.h Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
--- a/libpurple/protocols/gg/lib/COPYING Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/gg/lib/COPYING Wed Sep 12 19:11:38 2007 +0000 @@ -2,7 +2,7 @@ Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. @@ -485,7 +485,7 @@ You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA Also add information on how to contact you by electronic and paper mail.
--- a/libpurple/protocols/gg/lib/common.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/gg/lib/common.c Wed Sep 12 19:11:38 2007 +0000 @@ -15,7 +15,7 @@ * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, * USA. */
--- a/libpurple/protocols/gg/lib/compat.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/gg/lib/compat.h Wed Sep 12 19:11:38 2007 +0000 @@ -15,7 +15,7 @@ * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, * USA. */
--- a/libpurple/protocols/gg/lib/dcc.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/gg/lib/dcc.c Wed Sep 12 19:11:38 2007 +0000 @@ -15,7 +15,7 @@ * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, * USA. */
--- a/libpurple/protocols/gg/lib/events.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/gg/lib/events.c Wed Sep 12 19:11:38 2007 +0000 @@ -16,7 +16,7 @@ * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, * USA. */
--- a/libpurple/protocols/gg/lib/http.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/gg/lib/http.c Wed Sep 12 19:11:38 2007 +0000 @@ -14,7 +14,7 @@ * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, * USA. */
--- a/libpurple/protocols/gg/lib/libgadu.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/gg/lib/libgadu.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, * USA. */
--- a/libpurple/protocols/gg/lib/libgadu.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/gg/lib/libgadu.h Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, * USA. */
--- a/libpurple/protocols/gg/lib/obsolete.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/gg/lib/obsolete.c Wed Sep 12 19:11:38 2007 +0000 @@ -14,7 +14,7 @@ * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, * USA. */
--- a/libpurple/protocols/gg/lib/pubdir.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/gg/lib/pubdir.c Wed Sep 12 19:11:38 2007 +0000 @@ -15,7 +15,7 @@ * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, * USA. */
--- a/libpurple/protocols/gg/lib/pubdir50.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/gg/lib/pubdir50.c Wed Sep 12 19:11:38 2007 +0000 @@ -14,7 +14,7 @@ * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, * USA. */
--- a/libpurple/protocols/gg/search.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/gg/search.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
--- a/libpurple/protocols/gg/search.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/gg/search.h Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
--- a/libpurple/protocols/irc/cmds.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/irc/cmds.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "internal.h"
--- a/libpurple/protocols/irc/dcc_send.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/irc/dcc_send.c Wed Sep 12 19:11:38 2007 +0000 @@ -18,7 +18,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "internal.h"
--- a/libpurple/protocols/irc/irc.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/irc/irc.c Wed Sep 12 19:11:38 2007 +0000 @@ -20,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "internal.h" @@ -347,6 +347,7 @@ const char *username, *realname; struct irc_conn *irc = gc->proto_data; const char *pass = purple_connection_get_password(gc); + int ret; if (pass && *pass) { buf = irc_format(irc, "vv", "PASS", pass); @@ -359,8 +360,12 @@ } - gethostname(hostname, sizeof(hostname)); + ret = gethostname(hostname, sizeof(hostname)); hostname[sizeof(hostname) - 1] = '\0'; + if (ret < 0 || hostname[0] == '\0') { + purple_debug_warning("irc", "gethostname() failed -- is your hostname set?"); + strcpy(hostname, "localhost"); + } realname = purple_account_get_string(irc->account, "realname", ""); username = purple_account_get_string(irc->account, "username", ""); @@ -433,14 +438,7 @@ irc->gsc = NULL; - switch(error) { - case PURPLE_SSL_CONNECT_FAILED: - purple_connection_error(gc, _("Connection Failed")); - break; - case PURPLE_SSL_HANDSHAKE_FAILED: - purple_connection_error(gc, _("SSL Handshake Failed")); - break; - } + purple_connection_error(gc, purple_ssl_strerror(error)); } static void irc_close(PurpleConnection *gc)
--- a/libpurple/protocols/irc/irc.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/irc/irc.h Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _PURPLE_IRC_H
--- a/libpurple/protocols/irc/msgs.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/irc/msgs.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "internal.h"
--- a/libpurple/protocols/irc/parse.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/irc/parse.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "internal.h"
--- a/libpurple/protocols/jabber/Makefile.am Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/jabber/Makefile.am Wed Sep 12 19:11:38 2007 +0000 @@ -27,6 +27,8 @@ oob.h \ parser.c \ parser.h \ + ping.c \ + ping.h \ presence.c \ presence.h \ roster.c \ @@ -34,7 +36,19 @@ si.c \ si.h \ xdata.c \ - xdata.h + xdata.h \ + caps.c \ + caps.h \ + adhoccommands.c \ + adhoccommands.h \ + pep.c \ + pep.h \ + usermood.c \ + usermood.h \ + usernick.c \ + usernick.h \ + usertune.c \ + usertune.h AM_CFLAGS = $(st)
--- a/libpurple/protocols/jabber/Makefile.mingw Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/jabber/Makefile.mingw Wed Sep 12 19:11:38 2007 +0000 @@ -42,8 +42,11 @@ ## ## SOURCES, OBJECTS ## -C_SRC = auth.c \ +C_SRC = \ + adhoccommands.c \ + auth.c \ buddy.c \ + caps.c \ chat.c \ disco.c \ google.c \ @@ -53,9 +56,14 @@ message.c \ oob.c \ parser.c \ + pep.c \ + ping.c \ presence.c \ roster.c \ si.c \ + usermood.c \ + usernick.c \ + usertune.c \ xdata.c \ win32/posix.uname.c
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/adhoccommands.c Wed Sep 12 19:11:38 2007 +0000 @@ -0,0 +1,309 @@ +/* + * purple - Jabber Protocol Plugin + * + * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "internal.h" + +#include "adhoccommands.h" +#include <assert.h> +#include <string.h> +#include "internal.h" +#include "xdata.h" +#include "iq.h" +#include "request.h" + +static void do_adhoc_ignoreme(JabberStream *js, ...) { + /* we don't have to do anything */ +} + +typedef struct _JabberAdHocActionInfo { + char *sessionid; + char *who; + char *node; + GList *actionslist; +} JabberAdHocActionInfo; + +void jabber_adhoc_disco_result_cb(JabberStream *js, xmlnode *packet, gpointer data) { + const char *from = xmlnode_get_attrib(packet, "from"); + const char *type = xmlnode_get_attrib(packet, "type"); + const char *node; + xmlnode *query, *item; + JabberID *jabberid; + JabberBuddy *jb; + JabberBuddyResource *jbr = NULL; + + if(strcmp(type, "result")) + return; + + query = xmlnode_get_child_with_namespace(packet,"query","http://jabber.org/protocol/disco#items"); + if(!query) + return; + node = xmlnode_get_attrib(query,"node"); + if(!node || strcmp(node, "http://jabber.org/protocol/commands")) + return; + + if((jabberid = jabber_id_new(from))) { + if(jabberid->resource && (jb = jabber_buddy_find(js, from, TRUE))) + jbr = jabber_buddy_find_resource(jb, jabberid->resource); + jabber_id_free(jabberid); + } + + if(!jbr) + return; + + if(jbr->commands) { + /* since the list we just received is complete, wipe the old one */ + while(jbr->commands) { + JabberAdHocCommands *cmd = jbr->commands->data; + g_free(cmd->jid); + g_free(cmd->node); + g_free(cmd->name); + g_free(cmd); + jbr->commands = g_list_delete_link(jbr->commands, jbr->commands); + } + } + + for(item = query->child; item; item = item->next) { + JabberAdHocCommands *cmd; + if(item->type != XMLNODE_TYPE_TAG) + continue; + if(strcmp(item->name, "item")) + continue; + cmd = g_new0(JabberAdHocCommands, 1); + + cmd->jid = g_strdup(xmlnode_get_attrib(item,"jid")); + cmd->node = g_strdup(xmlnode_get_attrib(item,"node")); + cmd->name = g_strdup(xmlnode_get_attrib(item,"name")); + + jbr->commands = g_list_append(jbr->commands,cmd); + } +} + +static void jabber_adhoc_parse(JabberStream *js, xmlnode *packet, gpointer data); + +static void do_adhoc_action_cb(JabberStream *js, xmlnode *result, const char *actionhandle, gpointer user_data) { + xmlnode *command; + GList *action; + JabberAdHocActionInfo *actionInfo = user_data; + JabberIq *iq = jabber_iq_new(js, JABBER_IQ_SET); + jabber_iq_set_callback(iq, jabber_adhoc_parse, NULL); + + xmlnode_set_attrib(iq->node, "to", actionInfo->who); + command = xmlnode_new_child(iq->node,"command"); + xmlnode_set_namespace(command,"http://jabber.org/protocol/commands"); + xmlnode_set_attrib(command,"sessionid",actionInfo->sessionid); + xmlnode_set_attrib(command,"node",actionInfo->node); + if(actionhandle) + xmlnode_set_attrib(command,"action",actionhandle); + xmlnode_insert_child(command,result); + + for(action = actionInfo->actionslist; action; action = g_list_next(action)) { + char *handle = action->data; + g_free(handle); + } + g_list_free(actionInfo->actionslist); + g_free(actionInfo->sessionid); + g_free(actionInfo->who); + g_free(actionInfo->node); + + jabber_iq_send(iq); +} + +static void jabber_adhoc_parse(JabberStream *js, xmlnode *packet, gpointer data) { + xmlnode *command = xmlnode_get_child_with_namespace(packet, "command", "http://jabber.org/protocol/commands"); + const char *status = xmlnode_get_attrib(command,"status"); + xmlnode *xdata = xmlnode_get_child_with_namespace(command,"x","jabber:x:data"); + const char *type = xmlnode_get_attrib(packet,"type"); + + if(type && !strcmp(type,"error")) { + char *msg = jabber_parse_error(js, packet); + if(!msg) + msg = g_strdup(_("Unknown Error")); + + purple_notify_error(NULL, _("Ad-Hoc Command Failed"), + _("Ad-Hoc Command Failed"), msg); + g_free(msg); + return; + } + if(!type || strcmp(type,"result")) + return; + + if(!status) + return; + + if(!strcmp(status,"completed")) { + /* display result */ + xmlnode *note = xmlnode_get_child(command,"note"); + + if(note) + purple_notify_info(NULL, xmlnode_get_attrib(packet, "from"), xmlnode_get_data(note), NULL); + + if(xdata) + jabber_x_data_request(js, xdata, (jabber_x_data_cb)do_adhoc_ignoreme, NULL); + return; + } + if(!strcmp(status,"executing")) { + /* this command needs more steps */ + xmlnode *actions, *action; + int actionindex = 0; + GList *actionslist = NULL; + JabberAdHocActionInfo *actionInfo; + if(!xdata) + return; /* shouldn't happen */ + + actions = xmlnode_get_child(command,"actions"); + if(!actions) { + JabberXDataAction *defaultaction = g_new0(JabberXDataAction, 1); + defaultaction->name = g_strdup(_("execute")); + defaultaction->handle = g_strdup("execute"); + actionslist = g_list_append(actionslist, defaultaction); + } else { + const char *defaultactionhandle = xmlnode_get_attrib(actions, "execute"); + int index = 0; + for(action = actions->child; action; action = action->next, ++index) { + if(action->type == XMLNODE_TYPE_TAG) { + JabberXDataAction *newaction = g_new0(JabberXDataAction, 1); + newaction->name = g_strdup(_(action->name)); + newaction->handle = g_strdup(action->name); + actionslist = g_list_append(actionslist, newaction); + if(defaultactionhandle && !strcmp(defaultactionhandle, action->name)) + actionindex = index; + } + } + } + + actionInfo = g_new0(JabberAdHocActionInfo, 1); + actionInfo->sessionid = g_strdup(xmlnode_get_attrib(command,"sessionid")); + actionInfo->who = g_strdup(xmlnode_get_attrib(packet,"from")); + actionInfo->node = g_strdup(xmlnode_get_attrib(command,"node")); + actionInfo->actionslist = actionslist; + + jabber_x_data_request_with_actions(js,xdata,actionslist,actionindex,do_adhoc_action_cb,actionInfo); + } +} + +void jabber_adhoc_execute_action(PurpleBlistNode *node, gpointer data) { + if (PURPLE_BLIST_NODE_IS_BUDDY(node)) { + JabberAdHocCommands *cmd = data; + PurpleBuddy *buddy = (PurpleBuddy *) node; + JabberStream *js = purple_account_get_connection(buddy->account)->proto_data; + + jabber_adhoc_execute(js, cmd); + } +} + +static void jabber_adhoc_server_got_list_cb(JabberStream *js, xmlnode *packet, gpointer data) { + xmlnode *query = xmlnode_get_child_with_namespace(packet, "query", "http://jabber.org/protocol/disco#items"); + xmlnode *item; + + if(!query) + return; + + /* clean current list (just in case there is one) */ + while(js->commands) { + JabberAdHocCommands *cmd = js->commands->data; + g_free(cmd->jid); + g_free(cmd->node); + g_free(cmd->node); + g_free(cmd); + js->commands = g_list_delete_link(js->commands, js->commands); + } + + /* re-fill list */ + for(item = query->child; item; item = item->next) { + JabberAdHocCommands *cmd; + if(item->type != XMLNODE_TYPE_TAG) + continue; + if(strcmp(item->name, "item")) + continue; + cmd = g_new0(JabberAdHocCommands, 1); + cmd->jid = g_strdup(xmlnode_get_attrib(item,"jid")); + cmd->node = g_strdup(xmlnode_get_attrib(item,"node")); + cmd->name = g_strdup(xmlnode_get_attrib(item,"name")); + + js->commands = g_list_append(js->commands,cmd); + } +} + +void jabber_adhoc_server_get_list(JabberStream *js) { + JabberIq *iq = jabber_iq_new_query(js,JABBER_IQ_GET,"http://jabber.org/protocol/disco#items"); + xmlnode *query = xmlnode_get_child_with_namespace(iq->node,"query","http://jabber.org/protocol/disco#items"); + + xmlnode_set_attrib(iq->node,"to",js->user->domain); + xmlnode_set_attrib(query,"node","http://jabber.org/protocol/commands"); + + jabber_iq_set_callback(iq,jabber_adhoc_server_got_list_cb,NULL); + jabber_iq_send(iq); +} + +void jabber_adhoc_execute(JabberStream *js, JabberAdHocCommands *cmd) { + JabberIq *iq = jabber_iq_new(js, JABBER_IQ_SET); + xmlnode *command = xmlnode_new_child(iq->node,"command"); + xmlnode_set_attrib(iq->node,"to",cmd->jid); + xmlnode_set_namespace(command,"http://jabber.org/protocol/commands"); + xmlnode_set_attrib(command,"node",cmd->node); + xmlnode_set_attrib(command,"action","execute"); + + jabber_iq_set_callback(iq,jabber_adhoc_parse,NULL); + + jabber_iq_send(iq); +} + +static void jabber_adhoc_server_execute(PurplePluginAction *action) { + JabberAdHocCommands *cmd = action->user_data; + if(cmd) { + PurpleConnection *gc = (PurpleConnection *) action->context; + JabberStream *js = gc->proto_data; + + jabber_adhoc_execute(js, cmd); + } +} + +void jabber_adhoc_init_server_commands(JabberStream *js, GList **m) { + GList *cmdlst; + JabberBuddy *jb; + + /* also add commands for other clients connected to the same account on another resource */ + char *accountname = g_strdup_printf("%s@%s", js->user->node, js->user->domain); + if((jb = jabber_buddy_find(js, accountname, TRUE))) { + GList *iter; + for(iter = jb->resources; iter; iter = g_list_next(iter)) { + JabberBuddyResource *jbr = iter->data; + GList *riter; + for(riter = jbr->commands; riter; riter = g_list_next(riter)) { + JabberAdHocCommands *cmd = riter->data; + char *cmdname = g_strdup_printf("%s (%s)",cmd->name,jbr->name); + PurplePluginAction *act = purple_plugin_action_new(cmdname, jabber_adhoc_server_execute); + act->user_data = cmd; + *m = g_list_append(*m, act); + g_free(cmdname); + } + } + } + g_free(accountname); + + /* now add server commands */ + for(cmdlst = js->commands; cmdlst; cmdlst = g_list_next(cmdlst)) { + JabberAdHocCommands *cmd = cmdlst->data; + PurplePluginAction *act = purple_plugin_action_new(cmd->name, jabber_adhoc_server_execute); + act->user_data = cmd; + *m = g_list_append(*m, act); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/adhoccommands.h Wed Sep 12 19:11:38 2007 +0000 @@ -0,0 +1,39 @@ +/* + * purple - Jabber Protocol Plugin + * + * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _PURPLE_JABBER_ADHOCCOMMANDS_H_ +#define _PURPLE_JABBER_ADHOCCOMMANDS_H_ + +#include "jabber.h" + +/* Implementation of XEP-0050 */ + +void jabber_adhoc_disco_result_cb(JabberStream *js, xmlnode *packet, gpointer data); + +void jabber_adhoc_execute(JabberStream *js, JabberAdHocCommands *cmd); + +void jabber_adhoc_execute_action(PurpleBlistNode *node, gpointer data); + +void jabber_adhoc_server_get_list(JabberStream *js); + +void jabber_adhoc_init_server_commands(JabberStream *js, GList **m); + +#endif /* _PURPLE_JABBER_ADHOCCOMMANDS_H_ */
--- a/libpurple/protocols/jabber/auth.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/jabber/auth.c Wed Sep 12 19:11:38 2007 +0000 @@ -15,7 +15,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */ #include "internal.h" @@ -203,8 +203,15 @@ return TRUE; } -static void auth_pass_cb(JabberStream *js, PurpleRequestFields *fields) +static void auth_pass_cb(PurpleConnection *conn, PurpleRequestFields *fields) { + JabberStream *js; + + /* The password prompt dialog doesn't get disposed if the account disconnects */ + if (!PURPLE_CONNECTION_IS_VALID(conn)) + return; + + js = conn->proto_data; if (!auth_pass_generic(js, fields)) return; @@ -217,8 +224,16 @@ } static void -auth_old_pass_cb(JabberStream *js, PurpleRequestFields *fields) +auth_old_pass_cb(PurpleConnection *conn, PurpleRequestFields *fields) { + JabberStream *js; + + /* The password prompt dialog doesn't get disposed if the account disconnects */ + if (!PURPLE_CONNECTION_IS_VALID(conn)) + return; + + js = conn->proto_data; + if (!auth_pass_generic(js, fields)) return; @@ -228,9 +243,17 @@ static void -auth_no_pass_cb(JabberStream *js, PurpleRequestFields *fields) +auth_no_pass_cb(PurpleConnection *conn, PurpleRequestFields *fields) { - purple_connection_error(js->gc, _("Password is required to sign on.")); + JabberStream *js; + + /* The password prompt dialog doesn't get disposed if the account disconnects */ + if (!PURPLE_CONNECTION_IS_VALID(conn)) + return; + + js = conn->proto_data; + + purple_connection_error(conn, _("Password is required to sign on.")); } static void jabber_auth_start_cyrus(JabberStream *js) @@ -283,7 +306,7 @@ */ if (!purple_account_get_password(js->gc->account)) { - purple_account_request_password(js->gc->account, G_CALLBACK(auth_pass_cb), G_CALLBACK(auth_no_pass_cb), js); + purple_account_request_password(js->gc->account, G_CALLBACK(auth_pass_cb), G_CALLBACK(auth_no_pass_cb), js->gc); return; /* If we've got a password, but aren't sending @@ -597,7 +620,7 @@ */ if (!purple_account_get_password(js->gc->account)) { - purple_account_request_password(js->gc->account, G_CALLBACK(auth_old_pass_cb), G_CALLBACK(auth_no_pass_cb), js); + purple_account_request_password(js->gc->account, G_CALLBACK(auth_old_pass_cb), G_CALLBACK(auth_no_pass_cb), js->gc); return; } #endif
--- a/libpurple/protocols/jabber/auth.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/jabber/auth.h Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _PURPLE_JABBER_AUTH_H_ #define _PURPLE_JABBER_AUTH_H_
--- a/libpurple/protocols/jabber/buddy.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/jabber/buddy.c Wed Sep 12 19:11:38 2007 +0000 @@ -15,7 +15,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */ #include "internal.h" @@ -34,6 +34,8 @@ #include "iq.h" #include "presence.h" #include "xdata.h" +#include "pep.h" +#include "adhoccommands.h" typedef struct { long idle_seconds; @@ -116,7 +118,6 @@ int priority, JabberBuddyState state, const char *status) { JabberBuddyResource *jbr = jabber_buddy_find_resource(jb, resource); - if(!jbr) { jbr = g_new0(JabberBuddyResource, 1); jbr->jb = jb; @@ -128,7 +129,7 @@ jbr->state = state; if(jbr->status) g_free(jbr->status); - if (status) + if (status) jbr->status = g_markup_escape_text(status, -1); else jbr->status = NULL; @@ -141,6 +142,17 @@ g_return_if_fail(jbr != NULL); jbr->jb->resources = g_list_remove(jbr->jb->resources, jbr); + + while(jbr->commands) { + JabberAdHocCommands *cmd = jbr->commands->data; + g_free(cmd->jid); + g_free(cmd->node); + g_free(cmd->name); + g_free(cmd); + jbr->commands = g_list_delete_link(jbr->commands, jbr->commands); + } + + jabber_caps_free_clientinfo(jbr->caps); g_free(jbr->name); g_free(jbr->status); @@ -411,7 +423,7 @@ if ((img = purple_buddy_icons_find_account_icon(gc->account))) { gconstpointer avatar_data; gsize avatar_len; - xmlnode *photo, *binval; + xmlnode *photo, *binval, *type; gchar *enc; int i; unsigned char hashval[20]; @@ -424,6 +436,8 @@ xmlnode_free(photo); } photo = xmlnode_new_child(vc_node, "PHOTO"); + type = xmlnode_new_child(photo, "TYPE"); + xmlnode_insert_data(type, "image/png", -1); binval = xmlnode_new_child(photo, "BINVAL"); enc = purple_base64_encode(avatar_data, avatar_len); @@ -452,9 +466,135 @@ void jabber_set_buddy_icon(PurpleConnection *gc, PurpleStoredImage *img) { + PurplePresence *gpresence; + PurpleStatus *status; + + if(((JabberStream*)gc->proto_data)->pep) { + /* XEP-0084: User Avatars */ + if(img) { + /* A PNG header, including the IHDR, but nothing else */ + const struct { + guchar signature[8]; /* must be hex 89 50 4E 47 0D 0A 1A 0A */ + struct { + guint32 length; /* must be 0x0d */ + guchar type[4]; /* must be 'I' 'H' 'D' 'R' */ + guint32 width; + guint32 height; + guchar bitdepth; + guchar colortype; + guchar compression; + guchar filter; + guchar interlace; + } ihdr; + } *png = purple_imgstore_get_data(img); /* ATTN: this is in network byte order! */ + + /* check if the data is a valid png file (well, at least to some extend) */ + if(png->signature[0] == 0x89 && + png->signature[1] == 0x50 && + png->signature[2] == 0x4e && + png->signature[3] == 0x47 && + png->signature[4] == 0x0d && + png->signature[5] == 0x0a && + png->signature[6] == 0x1a && + png->signature[7] == 0x0a && + ntohl(png->ihdr.length) == 0x0d && + png->ihdr.type[0] == 'I' && + png->ihdr.type[1] == 'H' && + png->ihdr.type[2] == 'D' && + png->ihdr.type[3] == 'R') { + /* parse PNG header to get the size of the image (yes, this is required) */ + guint32 width = ntohl(png->ihdr.width); + guint32 height = ntohl(png->ihdr.height); + xmlnode *publish, *item, *data, *metadata, *info; + char *lengthstring, *widthstring, *heightstring; + + /* compute the sha1 hash */ + PurpleCipherContext *ctx; + unsigned char digest[20]; + char *hash; + char *base64avatar; + + ctx = purple_cipher_context_new_by_name("sha1", NULL); + purple_cipher_context_append(ctx, purple_imgstore_get_data(img), purple_imgstore_get_size(img)); + purple_cipher_context_digest(ctx, sizeof(digest), digest, NULL); + + /* convert digest to a string */ + hash = g_strdup_printf("%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x",digest[0],digest[1],digest[2],digest[3],digest[4],digest[5],digest[6],digest[7],digest[8],digest[9],digest[10],digest[11],digest[12],digest[13],digest[14],digest[15],digest[16],digest[17],digest[18],digest[19]); + + publish = xmlnode_new("publish"); + xmlnode_set_attrib(publish,"node",AVATARNAMESPACEDATA); + + item = xmlnode_new_child(publish, "item"); + xmlnode_set_attrib(item, "id", hash); + + data = xmlnode_new_child(item, "data"); + xmlnode_set_namespace(data,AVATARNAMESPACEDATA); + + base64avatar = purple_base64_encode(purple_imgstore_get_data(img), purple_imgstore_get_size(img)); + xmlnode_insert_data(data,base64avatar,-1); + g_free(base64avatar); + + /* publish the avatar itself */ + jabber_pep_publish((JabberStream*)gc->proto_data, publish); + + /* next step: publish the metadata */ + publish = xmlnode_new("publish"); + xmlnode_set_attrib(publish,"node",AVATARNAMESPACEMETA); + + item = xmlnode_new_child(publish, "item"); + xmlnode_set_attrib(item, "id", hash); + + metadata = xmlnode_new_child(item, "metadata"); + xmlnode_set_namespace(metadata,AVATARNAMESPACEMETA); + + info = xmlnode_new_child(metadata, "info"); + xmlnode_set_attrib(info, "id", hash); + xmlnode_set_attrib(info, "type", "image/png"); + lengthstring = g_strdup_printf("%u", (unsigned)purple_imgstore_get_size(img)); + xmlnode_set_attrib(info, "bytes", lengthstring); + g_free(lengthstring); + widthstring = g_strdup_printf("%u", width); + xmlnode_set_attrib(info, "width", widthstring); + g_free(widthstring); + heightstring = g_strdup_printf("%u", height); + xmlnode_set_attrib(info, "height", heightstring); + g_free(lengthstring); + + /* publish the metadata */ + jabber_pep_publish((JabberStream*)gc->proto_data, publish); + + g_free(hash); + } else { /* if(img) */ + /* remove the metadata */ + xmlnode *metadata, *item; + xmlnode *publish = xmlnode_new("publish"); + xmlnode_set_attrib(publish,"node",AVATARNAMESPACEMETA); + + item = xmlnode_new_child(publish, "item"); + + metadata = xmlnode_new_child(item, "metadata"); + xmlnode_set_namespace(metadata,AVATARNAMESPACEMETA); + + xmlnode_new_child(metadata, "stop"); + + /* publish the metadata */ + jabber_pep_publish((JabberStream*)gc->proto_data, publish); + } + } else { + purple_debug(PURPLE_DEBUG_ERROR, "jabber", + "jabber_set_buddy_icon received non-png data"); + } + } + + /* even when the image is not png, we can still publish the vCard, since this + one doesn't require a specific image type */ + + /* publish vCard for those poor older clients */ jabber_set_info(gc, purple_account_get_user_info(gc->account)); - jabber_presence_send(gc->account, NULL); + gpresence = purple_account_get_presence(gc->account); + status = purple_presence_get_active_status(gpresence); + jabber_presence_send(gc->account, status); } /* @@ -659,6 +799,130 @@ purple_notify_user_info_add_pair(user_info, _("Operating System"), jbr->client.os); } } +#if 0 + /* #if 0 this for now; I think this would be far more useful if we limited this to a particular set of features + * of particular interest (-vv jumps out as one). As it is now, I don't picture people getting all excited: "Oh sweet crap! + * So-and-so supports 'jabber:x:data' AND 'Collaborative Data Objects'!" + */ + + if(jbr && jbr->caps) { + GString *tmp = g_string_new(""); + GList *iter; + for(iter = jbr->caps->features; iter; iter = g_list_next(iter)) { + const char *feature = iter->data; + + if(!strcmp(feature, "jabber:iq:last")) + feature = _("Last Activity"); + else if(!strcmp(feature, "http://jabber.org/protocol/disco#info")) + feature = _("Service Discovery Info"); + else if(!strcmp(feature, "http://jabber.org/protocol/disco#items")) + feature = _("Service Discovery Items"); + else if(!strcmp(feature, "http://jabber.org/protocol/address")) + feature = _("Extended Stanza Addressing"); + else if(!strcmp(feature, "http://jabber.org/protocol/muc")) + feature = _("Multi-User Chat"); + else if(!strcmp(feature, "http://jabber.org/protocol/muc#user")) + feature = _("Multi-User Chat Extended Presence Information"); + else if(!strcmp(feature, "http://jabber.org/protocol/ibb")) + feature = _("In-Band Bytestreams"); + else if(!strcmp(feature, "http://jabber.org/protocol/commands")) + feature = _("Ad-Hoc Commands"); + else if(!strcmp(feature, "http://jabber.org/protocol/pubsub")) + feature = _("PubSub Service"); + else if(!strcmp(feature, "http://jabber.org/protocol/bytestreams")) + feature = _("SOCKS5 Bytestreams"); + else if(!strcmp(feature, "jabber:x:oob")) + feature = _("Out of Band Data"); + else if(!strcmp(feature, "http://jabber.org/protocol/xhtml-im")) + feature = _("XHTML-IM"); + else if(!strcmp(feature, "jabber:iq:register")) + feature = _("In-Band Registration"); + else if(!strcmp(feature, "http://jabber.org/protocol/geoloc")) + feature = _("User Location"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0084.html")) + feature = _("User Avatar"); + else if(!strcmp(feature, "http://jabber.org/protocol/chatstates")) + feature = _("Chat State Notifications"); + else if(!strcmp(feature, "jabber:iq:version")) + feature = _("Software Version"); + else if(!strcmp(feature, "http://jabber.org/protocol/si")) + feature = _("Stream Initiation"); + else if(!strcmp(feature, "http://jabber.org/protocol/si/profile/file-transfer")) + feature = _("File Transfer"); + else if(!strcmp(feature, "http://jabber.org/protocol/mood")) + feature = _("User Mood"); + else if(!strcmp(feature, "http://jabber.org/protocol/activity")) + feature = _("User Activity"); + else if(!strcmp(feature, "http://jabber.org/protocol/caps")) + feature = _("Entity Capabilities"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0116.html")) + feature = _("Encrypted Session Negotiations"); + else if(!strcmp(feature, "http://jabber.org/protocol/tune")) + feature = _("User Tune"); + else if(!strcmp(feature, "http://jabber.org/protocol/rosterx")) + feature = _("Roster Item Exchange"); + else if(!strcmp(feature, "http://jabber.org/protocol/reach")) + feature = _("Reachability Address"); + else if(!strcmp(feature, "http://jabber.org/protocol/profile")) + feature = _("User Profile"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0166.html#ns")) + feature = _("Jingle"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0167.html#ns")) + feature = _("Jingle Audio"); + else if(!strcmp(feature, "http://jabber.org/protocol/nick")) + feature = _("User Nickname"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0176.html#ns-udp")) + feature = _("Jingle ICE UDP"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0176.html#ns-tcp")) + feature = _("Jingle ICE TCP"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0177.html#ns")) + feature = _("Jingle Raw UDP"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0180.html#ns")) + feature = _("Jingle Video"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0181.html#ns")) + feature = _("Jingle DTMF"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0184.html#ns")) + feature = _("Message Receipts"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0189.html#ns")) + feature = _("Public Key Publishing"); + else if(!strcmp(feature, "http://jabber.org/protocol/chatting")) + feature = _("User Chatting"); + else if(!strcmp(feature, "http://jabber.org/protocol/browsing")) + feature = _("User Browsing"); + else if(!strcmp(feature, "http://jabber.org/protocol/gaming")) + feature = _("User Gaming"); + else if(!strcmp(feature, "http://jabber.org/protocol/viewing")) + feature = _("User Viewing"); + else if(!strcmp(feature, "urn:xmpp:ping") || !strcmp(feature, "http://www.xmpp.org/extensions/xep-0199.html#ns")) + feature = _("Ping"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0200.html#ns")) + feature = _("Stanza Encryption"); + else if(!strcmp(feature, "urn:xmpp:time")) + feature = _("Entity Time"); + else if(!strcmp(feature, "urn:xmpp:delay")) + feature = _("Delayed Delivery"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0204.html#ns")) + feature = _("Collaborative Data Objects"); + else if(!strcmp(feature, "http://jabber.org/protocol/fileshare")) + feature = _("File Repository and Sharing"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0215.html#ns")) + feature = _("STUN Service Discovery for Jingle"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0116.html#ns")) + feature = _("Simplified Encrypted Session Negotiation"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0219.html#ns")) + feature = _("Hop Check"); + else if(g_str_has_suffix(feature, "+notify")) + feature = NULL; + if(feature) + g_string_append_printf(tmp, "%s<br/>", feature); + } + + if(strlen(tmp->str) > 0) + purple_notify_user_info_add_pair(user_info, _("Capabilities"), tmp->str); + + g_string_free(tmp, TRUE); + } +#endif } else { for(resources = jbi->jb->resources; resources; resources = resources->next) { char *purdy = NULL; @@ -700,6 +964,125 @@ purple_notify_user_info_add_pair(user_info, _("Operating System"), jbr->client.os); } } +#if 0 + if(jbr && jbr->caps) { + GString *tmp = g_string_new(""); + GList *iter; + for(iter = jbr->caps->features; iter; iter = g_list_next(iter)) { + const char *feature = iter->data; + + if(!strcmp(feature, "jabber:iq:last")) + feature = _("Last Activity"); + else if(!strcmp(feature, "http://jabber.org/protocol/disco#info")) + feature = _("Service Discovery Info"); + else if(!strcmp(feature, "http://jabber.org/protocol/disco#items")) + feature = _("Service Discovery Items"); + else if(!strcmp(feature, "http://jabber.org/protocol/address")) + feature = _("Extended Stanza Addressing"); + else if(!strcmp(feature, "http://jabber.org/protocol/muc")) + feature = _("Multi-User Chat"); + else if(!strcmp(feature, "http://jabber.org/protocol/muc#user")) + feature = _("Multi-User Chat Extended Presence Information"); + else if(!strcmp(feature, "http://jabber.org/protocol/ibb")) + feature = _("In-Band Bytestreams"); + else if(!strcmp(feature, "http://jabber.org/protocol/commands")) + feature = _("Ad-Hoc Commands"); + else if(!strcmp(feature, "http://jabber.org/protocol/pubsub")) + feature = _("PubSub Service"); + else if(!strcmp(feature, "http://jabber.org/protocol/bytestreams")) + feature = _("SOCKS5 Bytestreams"); + else if(!strcmp(feature, "jabber:x:oob")) + feature = _("Out of Band Data"); + else if(!strcmp(feature, "http://jabber.org/protocol/xhtml-im")) + feature = _("XHTML-IM"); + else if(!strcmp(feature, "jabber:iq:register")) + feature = _("In-Band Registration"); + else if(!strcmp(feature, "http://jabber.org/protocol/geoloc")) + feature = _("User Location"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0084.html")) + feature = _("User Avatar"); + else if(!strcmp(feature, "http://jabber.org/protocol/chatstates")) + feature = _("Chat State Notifications"); + else if(!strcmp(feature, "jabber:iq:version")) + feature = _("Software Version"); + else if(!strcmp(feature, "http://jabber.org/protocol/si")) + feature = _("Stream Initiation"); + else if(!strcmp(feature, "http://jabber.org/protocol/si/profile/file-transfer")) + feature = _("File Transfer"); + else if(!strcmp(feature, "http://jabber.org/protocol/mood")) + feature = _("User Mood"); + else if(!strcmp(feature, "http://jabber.org/protocol/activity")) + feature = _("User Activity"); + else if(!strcmp(feature, "http://jabber.org/protocol/caps")) + feature = _("Entity Capabilities"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0116.html")) + feature = _("Encrypted Session Negotiations"); + else if(!strcmp(feature, "http://jabber.org/protocol/tune")) + feature = _("User Tune"); + else if(!strcmp(feature, "http://jabber.org/protocol/rosterx")) + feature = _("Roster Item Exchange"); + else if(!strcmp(feature, "http://jabber.org/protocol/reach")) + feature = _("Reachability Address"); + else if(!strcmp(feature, "http://jabber.org/protocol/profile")) + feature = _("User Profile"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0166.html#ns")) + feature = _("Jingle"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0167.html#ns")) + feature = _("Jingle Audio"); + else if(!strcmp(feature, "http://jabber.org/protocol/nick")) + feature = _("User Nickname"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0176.html#ns-udp")) + feature = _("Jingle ICE UDP"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0176.html#ns-tcp")) + feature = _("Jingle ICE TCP"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0177.html#ns")) + feature = _("Jingle Raw UDP"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0180.html#ns")) + feature = _("Jingle Video"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0181.html#ns")) + feature = _("Jingle DTMF"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0184.html#ns")) + feature = _("Message Receipts"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0189.html#ns")) + feature = _("Public Key Publishing"); + else if(!strcmp(feature, "http://jabber.org/protocol/chatting")) + feature = _("User Chatting"); + else if(!strcmp(feature, "http://jabber.org/protocol/browsing")) + feature = _("User Browsing"); + else if(!strcmp(feature, "http://jabber.org/protocol/gaming")) + feature = _("User Gaming"); + else if(!strcmp(feature, "http://jabber.org/protocol/viewing")) + feature = _("User Viewing"); + else if(!strcmp(feature, "urn:xmpp:ping") || !strcmp(feature, "http://www.xmpp.org/extensions/xep-0199.html#ns")) + feature = _("Ping"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0200.html#ns")) + feature = _("Stanza Encryption"); + else if(!strcmp(feature, "urn:xmpp:time")) + feature = _("Entity Time"); + else if(!strcmp(feature, "urn:xmpp:delay")) + feature = _("Delayed Delivery"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0204.html#ns")) + feature = _("Collaborative Data Objects"); + else if(!strcmp(feature, "http://jabber.org/protocol/fileshare")) + feature = _("File Repository and Sharing"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0215.html#ns")) + feature = _("STUN Service Discovery for Jingle"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0116.html#ns")) + feature = _("Simplified Encrypted Session Negotiation"); + else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0219.html#ns")) + feature = _("Hop Check"); + else if(g_str_has_suffix(feature, "+notify")) + feature = NULL; + + if(feature) + g_string_append_printf(tmp, "%s\n", feature); + } + if(strlen(tmp->str) > 0) + purple_notify_user_info_add_pair(user_info, _("Capabilities"), tmp->str); + + g_string_free(tmp, TRUE); + } +#endif } } @@ -777,6 +1160,17 @@ jabber_iq_send(iq); } +static void +jabber_string_escape_and_append(GString *string, const char *name, const char *value, gboolean indent) +{ + gchar *escaped; + + escaped = g_markup_escape_text(value, -1); + g_string_append_printf(string, "%s<b>%s:</b> %s<br/>", + indent ? " " : "", name, escaped); + g_free(escaped); +} + static void jabber_vcard_parse(JabberStream *js, xmlnode *packet, gpointer data) { const char *id, *from; @@ -821,8 +1215,8 @@ text = xmlnode_get_data(child); if(text && !strcmp(child->name, "FN")) { - g_string_append_printf(info_text, "<b>%s:</b> %s<br/>", - _("Full Name"), text); + jabber_string_escape_and_append(info_text, + _("Full Name"), text, FALSE); } else if(!strcmp(child->name, "N")) { for(child2 = child->child; child2; child2 = child2->next) { @@ -833,17 +1227,14 @@ text2 = xmlnode_get_data(child2); if(text2 && !strcmp(child2->name, "FAMILY")) { - g_string_append_printf(info_text, - "<b>%s:</b> %s<br/>", - _("Family Name"), text2); + jabber_string_escape_and_append(info_text, + _("Family Name"), text2, FALSE); } else if(text2 && !strcmp(child2->name, "GIVEN")) { - g_string_append_printf(info_text, - "<b>%s:</b> %s<br/>", - _("Given Name"), text2); + jabber_string_escape_and_append(info_text, + _("Given Name"), text2, FALSE); } else if(text2 && !strcmp(child2->name, "MIDDLE")) { - g_string_append_printf(info_text, - "<b>%s:</b> %s<br/>", - _("Middle Name"), text2); + jabber_string_escape_and_append(info_text, + _("Middle Name"), text2, FALSE); } g_free(text2); } @@ -852,11 +1243,11 @@ if(b) { purple_blist_node_set_string((PurpleBlistNode*)b, "servernick", text); } - g_string_append_printf(info_text, "<b>%s:</b> %s<br/>", - _("Nickname"), text); + jabber_string_escape_and_append(info_text, + _("Nickname"), text, FALSE); } else if(text && !strcmp(child->name, "BDAY")) { - g_string_append_printf(info_text, "<b>%s:</b> %s<br/>", - _("Birthday"), text); + jabber_string_escape_and_append(info_text, + _("Birthday"), text, FALSE); } else if(!strcmp(child->name, "ADR")) { gboolean address_line_added = FALSE; @@ -881,34 +1272,27 @@ } if(!strcmp(child2->name, "POBOX")) { - g_string_append_printf(info_text, - " <b>%s:</b> %s<br/>", - _("P.O. Box"), text2); + jabber_string_escape_and_append(info_text, + _("P.O. Box"), text2, TRUE); } else if(!strcmp(child2->name, "EXTADR")) { - g_string_append_printf(info_text, - " <b>%s:</b> %s<br/>", - _("Extended Address"), text2); + jabber_string_escape_and_append(info_text, + _("Extended Address"), text2, TRUE); } else if(!strcmp(child2->name, "STREET")) { - g_string_append_printf(info_text, - " <b>%s:</b> %s<br/>", - _("Street Address"), text2); + jabber_string_escape_and_append(info_text, + _("Street Address"), text2, TRUE); } else if(!strcmp(child2->name, "LOCALITY")) { - g_string_append_printf(info_text, - " <b>%s:</b> %s<br/>", - _("Locality"), text2); + jabber_string_escape_and_append(info_text, + _("Locality"), text2, TRUE); } else if(!strcmp(child2->name, "REGION")) { - g_string_append_printf(info_text, - " <b>%s:</b> %s<br/>", - _("Region"), text2); + jabber_string_escape_and_append(info_text, + _("Region"), text2, TRUE); } else if(!strcmp(child2->name, "PCODE")) { - g_string_append_printf(info_text, - " <b>%s:</b> %s<br/>", - _("Postal Code"), text2); + jabber_string_escape_and_append(info_text, + _("Postal Code"), text2, TRUE); } else if(!strcmp(child2->name, "CTRY") || !strcmp(child2->name, "COUNTRY")) { - g_string_append_printf(info_text, - " <b>%s:</b> %s<br/>", - _("Country"), text2); + jabber_string_escape_and_append(info_text, + _("Country"), text2, TRUE); } g_free(text2); } @@ -918,34 +1302,38 @@ /* show what kind of number it is */ number = xmlnode_get_data(child2); if(number) { - g_string_append_printf(info_text, - "<b>%s:</b> %s<br/>", _("Telephone"), number); + jabber_string_escape_and_append(info_text, + _("Telephone"), number, FALSE); g_free(number); } } else if((number = xmlnode_get_data(child))) { /* lots of clients (including purple) do this, but it's * out of spec */ - g_string_append_printf(info_text, - "<b>%s:</b> %s<br/>", _("Telephone"), number); + jabber_string_escape_and_append(info_text, + _("Telephone"), number, FALSE); g_free(number); } } else if(!strcmp(child->name, "EMAIL")) { - char *userid; + char *userid, *escaped; if((child2 = xmlnode_get_child(child, "USERID"))) { /* show what kind of email it is */ userid = xmlnode_get_data(child2); if(userid) { + escaped = g_markup_escape_text(userid, -1); g_string_append_printf(info_text, - "<b>%s:</b> <a href='mailto:%s'>%s</a><br/>", - _("E-Mail"), userid, userid); + "<b>%s:</b> <a href=\"mailto:%s\">%s</a><br/>", + _("E-Mail"), escaped, escaped); + g_free(escaped); g_free(userid); } } else if((userid = xmlnode_get_data(child))) { /* lots of clients (including purple) do this, but it's * out of spec */ - g_string_append_printf(info_text, - "<b>%s:</b> <a href='mailto:%s'>%s</a><br/>", - _("E-Mail"), userid, userid); + escaped = g_markup_escape_text(userid, -1); + g_string_append_printf(info_text, + "<b>%s:</b> <a href=\"mailto:%s\">%s</a><br/>", + _("E-Mail"), escaped, escaped); + g_free(escaped); g_free(userid); } } else if(!strcmp(child->name, "ORG")) { @@ -958,25 +1346,23 @@ text2 = xmlnode_get_data(child2); if(text2 && !strcmp(child2->name, "ORGNAME")) { - g_string_append_printf(info_text, - "<b>%s:</b> %s<br/>", - _("Organization Name"), text2); + jabber_string_escape_and_append(info_text, + _("Organization Name"), text2, FALSE); } else if(text2 && !strcmp(child2->name, "ORGUNIT")) { - g_string_append_printf(info_text, - "<b>%s:</b> %s<br/>", - _("Organization Unit"), text2); + jabber_string_escape_and_append(info_text, + _("Organization Unit"), text2, FALSE); } g_free(text2); } } else if(text && !strcmp(child->name, "TITLE")) { - g_string_append_printf(info_text, "<b>%s:</b> %s<br/>", - _("Title"), text); + jabber_string_escape_and_append(info_text, + _("Title"), text, FALSE); } else if(text && !strcmp(child->name, "ROLE")) { - g_string_append_printf(info_text, "<b>%s:</b> %s<br/>", - _("Role"), text); + jabber_string_escape_and_append(info_text, + _("Role"), text, FALSE); } else if(text && !strcmp(child->name, "DESC")) { - g_string_append_printf(info_text, "<b>%s:</b> %s<br/>", - _("Description"), text); + jabber_string_escape_and_append(info_text, + _("Description"), text, FALSE); } else if(!strcmp(child->name, "PHOTO") || !strcmp(child->name, "LOGO")) { char *bintext = NULL; @@ -1023,6 +1409,109 @@ jabber_buddy_info_show_if_ready(jbi); } +typedef struct _JabberBuddyAvatarUpdateURLInfo { + JabberStream *js; + char *from; + char *id; +} JabberBuddyAvatarUpdateURLInfo; + +static void do_buddy_avatar_update_fromurl(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message) { + JabberBuddyAvatarUpdateURLInfo *info = user_data; + if(!url_text) { + purple_debug(PURPLE_DEBUG_ERROR, "jabber", + "do_buddy_avatar_update_fromurl got error \"%s\"", error_message); + return; + } + + purple_buddy_icons_set_for_user(purple_connection_get_account(info->js->gc), info->from, (void*)url_text, len, info->id); + g_free(info->from); + g_free(info->id); + g_free(info); +} + +static void do_buddy_avatar_update_data(JabberStream *js, const char *from, xmlnode *items) { + xmlnode *item, *data; + const char *checksum; + char *b64data; + void *img; + size_t size; + if(!items) + return; + + item = xmlnode_get_child(items, "item"); + if(!item) + return; + + data = xmlnode_get_child_with_namespace(item,"data",AVATARNAMESPACEDATA); + if(!data) + return; + + checksum = xmlnode_get_attrib(item,"id"); + if(!checksum) + return; + + b64data = xmlnode_get_data(data); + if(!b64data) + return; + + img = purple_base64_decode(b64data, &size); + if(!img) + return; + + purple_buddy_icons_set_for_user(purple_connection_get_account(js->gc), from, img, size, checksum); +} + +void jabber_buddy_avatar_update_metadata(JabberStream *js, const char *from, xmlnode *items) { + PurpleBuddy *buddy = purple_find_buddy(purple_connection_get_account(js->gc), from); + const char *checksum; + xmlnode *item, *metadata; + if(!buddy) + return; + + checksum = purple_buddy_icons_get_checksum_for_user(buddy); + item = xmlnode_get_child(items,"item"); + metadata = xmlnode_get_child_with_namespace(item, "metadata", AVATARNAMESPACEMETA); + if(!metadata) + return; + /* check if we have received a stop */ + if(xmlnode_get_child(metadata, "stop")) { + purple_buddy_icons_set_for_user(purple_connection_get_account(js->gc), from, NULL, 0, NULL); + } else { + xmlnode *info, *goodinfo = NULL; + + /* iterate over all info nodes to get one we can use */ + for(info = metadata->child; info; info = info->next) { + if(info->type == XMLNODE_TYPE_TAG && !strcmp(info->name,"info")) { + const char *type = xmlnode_get_attrib(info,"type"); + const char *id = xmlnode_get_attrib(info,"id"); + + if(checksum && id && !strcmp(id, checksum)) { + /* we already have that avatar, so we don't have to do anything */ + goodinfo = NULL; + break; + } + /* We'll only pick the png one for now. It's a very nice image format anyways. */ + if(type && id && !goodinfo && !strcmp(type, "image/png")) + goodinfo = info; + } + } + if(goodinfo) { + const char *url = xmlnode_get_attrib(goodinfo,"url"); + const char *id = xmlnode_get_attrib(goodinfo,"id"); + + /* the avatar might either be stored in a pep node, or on a HTTP/HTTPS URL */ + if(!url) + jabber_pep_request_item(js, from, AVATARNAMESPACEDATA, id, do_buddy_avatar_update_data); + else { + JabberBuddyAvatarUpdateURLInfo *info = g_new0(JabberBuddyAvatarUpdateURLInfo, 1); + info->js = js; + info->from = g_strdup(from); + info->id = g_strdup(id); + purple_util_fetch_url(url, TRUE, NULL, TRUE, do_buddy_avatar_update_fromurl, info); + } + } + } +} static void jabber_buddy_info_resource_free(gpointer data) { @@ -1295,7 +1784,7 @@ status = purple_presence_get_active_status(gpresence); purple_status_to_jabber(status, &state, &msg, &priority); - presence = jabber_presence_create(state, msg, priority); + presence = jabber_presence_create_js(js, state, msg, priority); g_free(msg); @@ -1389,12 +1878,54 @@ jabber_presence_subscription_set(js, buddy->name, "unsubscribe"); } +static void jabber_buddy_login(PurpleBlistNode *node, gpointer data) { + if(PURPLE_BLIST_NODE_IS_BUDDY(node)) { + /* simply create a directed presence of the current status */ + PurpleBuddy *buddy = (PurpleBuddy *) node; + PurpleConnection *gc = purple_account_get_connection(buddy->account); + JabberStream *js = gc->proto_data; + PurpleAccount *account = purple_connection_get_account(gc); + PurplePresence *gpresence = purple_account_get_presence(account); + PurpleStatus *status = purple_presence_get_active_status(gpresence); + xmlnode *presence; + JabberBuddyState state; + char *msg; + int priority; + + purple_status_to_jabber(status, &state, &msg, &priority); + presence = jabber_presence_create_js(js, state, msg, priority); + + g_free(msg); + + xmlnode_set_attrib(presence, "to", buddy->name); + + jabber_send(js, presence); + xmlnode_free(presence); + } +} + +static void jabber_buddy_logout(PurpleBlistNode *node, gpointer data) { + if(PURPLE_BLIST_NODE_IS_BUDDY(node)) { + /* simply create a directed unavailable presence */ + PurpleBuddy *buddy = (PurpleBuddy *) node; + JabberStream *js = purple_account_get_connection(buddy->account)->proto_data; + xmlnode *presence; + + presence = jabber_presence_create_js(js, JABBER_BUDDY_STATE_UNAVAILABLE, NULL, 0); + + xmlnode_set_attrib(presence, "to", buddy->name); + + jabber_send(js, presence); + xmlnode_free(presence); + } +} static GList *jabber_buddy_menu(PurpleBuddy *buddy) { PurpleConnection *gc = purple_account_get_connection(buddy->account); JabberStream *js = gc->proto_data; JabberBuddy *jb = jabber_buddy_find(js, buddy->name, TRUE); + GList *jbrs; GList *m = NULL; PurpleMenuAction *act; @@ -1439,6 +1970,38 @@ NULL, NULL); m = g_list_append(m, act); } + + /* + * This if-condition implements parts of XEP-0100: Gateway Interaction + * + * According to stpeter, there is no way to know if a jid on the roster is a gateway without sending a disco#info. + * However, since the gateway might appear offline to us, we cannot get that information. Therefore, I just assume + * that gateways on the roster can be identified by having no '@' in their jid. This is a faily safe assumption, since + * people don't tend to have a server or other service there. + */ + if (g_utf8_strchr(buddy->name, -1, '@') == NULL) { + act = purple_menu_action_new(_("Log In"), + PURPLE_CALLBACK(jabber_buddy_login), + NULL, NULL); + m = g_list_append(m, act); + act = purple_menu_action_new(_("Log Out"), + PURPLE_CALLBACK(jabber_buddy_logout), + NULL, NULL); + m = g_list_append(m, act); + } + + /* add all ad hoc commands to the action menu */ + for(jbrs = jb->resources; jbrs; jbrs = g_list_next(jbrs)) { + JabberBuddyResource *jbr = jbrs->data; + GList *commands; + if (!jbr->commands) + continue; + for(commands = jbr->commands; commands; commands = g_list_next(commands)) { + JabberAdHocCommands *cmd = commands->data; + act = purple_menu_action_new(cmd->name, PURPLE_CALLBACK(jabber_adhoc_execute_action), cmd, NULL); + m = g_list_append(m, act); + } + } return m; } @@ -1584,36 +2147,58 @@ results = purple_notify_searchresults_new(); if((x = xmlnode_get_child_with_namespace(query, "x", "jabber:x:data"))) { xmlnode *reported; + GSList *column_vars = NULL; + purple_debug_info("jabber", "new-skool\n"); + if((reported = xmlnode_get_child(x, "reported"))) { xmlnode *field = xmlnode_get_child(reported, "field"); while(field) { - /* XXX keep track of this order, use it below */ const char *var = xmlnode_get_attrib(field, "var"); const char *label = xmlnode_get_attrib(field, "label"); if(var) { column = purple_notify_searchresults_column_new(label ? label : var); purple_notify_searchresults_column_add(results, column); + column_vars = g_slist_append(column_vars, (char *)var); } field = xmlnode_get_next_twin(field); } } + item = xmlnode_get_child(x, "item"); while(item) { GList *row = NULL; - field = xmlnode_get_child(item, "field"); - while(field) { - xmlnode *valuenode = xmlnode_get_child(field, "value"); - if(valuenode) { - char *value = xmlnode_get_data(valuenode); - row = g_list_append(row, value); + GSList *l; + xmlnode *valuenode; + const char *var; + + for (l = column_vars; l != NULL; l = l->next) { + /* + * Build a row containing the strings that correspond + * to each column of the search results. + */ + for (field = xmlnode_get_child(item, "field"); + field != NULL; + field = xmlnode_get_next_twin(field)) + { + if ((var = xmlnode_get_attrib(field, "var")) && + !strcmp(var, l->data) && + (valuenode = xmlnode_get_child(field, "value"))) + { + char *value = xmlnode_get_data(valuenode); + row = g_list_append(row, value); + break; + } } - field = xmlnode_get_next_twin(field); + if (field == NULL) + /* No data for this column */ + row = g_list_append(row, NULL); } purple_notify_searchresults_row_add(results, row); - item = xmlnode_get_next_twin(item); } + + g_slist_free(column_vars); } else { /* old skool */ purple_debug_info("jabber", "old-skool\n"); @@ -1728,10 +2313,10 @@ * in purple-i18n@lists.sourceforge.net (March 2006) */ static const char * jabber_user_dir_comments [] = { - /* current comment from Jabber User Directory users.jabber.org */ - N_("Find a contact by entering the search criteria in the given fields. " - "Note: Each field supports wild card searches (%)"), - NULL + /* current comment from Jabber User Directory users.jabber.org */ + N_("Find a contact by entering the search criteria in the given fields. " + "Note: Each field supports wild card searches (%)"), + NULL }; #endif @@ -1824,14 +2409,14 @@ _("Search for XMPP users"), instructions, fields, _("Search"), G_CALLBACK(user_search_cb), _("Cancel"), G_CALLBACK(user_search_cancel_cb), - NULL, NULL, NULL, + purple_connection_get_account(js->gc), NULL, NULL, "account", usi); g_free(instructions); } } -static void jabber_user_search_ok(JabberStream *js, const char *directory) +void jabber_user_search(JabberStream *js, const char *directory) { JabberIq *iq; @@ -1858,7 +2443,7 @@ _("Select a user directory to search"), js->user_directories ? js->user_directories->data : NULL, FALSE, FALSE, NULL, - _("Search Directory"), PURPLE_CALLBACK(jabber_user_search_ok), + _("Search Directory"), PURPLE_CALLBACK(jabber_user_search), _("Cancel"), NULL, NULL, NULL, NULL, "account", js);
--- a/libpurple/protocols/jabber/buddy.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/jabber/buddy.h Wed Sep 12 19:11:38 2007 +0000 @@ -17,13 +17,11 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _PURPLE_JABBER_BUDDY_H_ #define _PURPLE_JABBER_BUDDY_H_ -#include "jabber.h" - typedef enum { JABBER_BUDDY_STATE_UNKNOWN = -2, JABBER_BUDDY_STATE_ERROR = -1, @@ -35,6 +33,12 @@ JABBER_BUDDY_STATE_DND } JabberBuddyState; +#include "jabber.h" +#include "caps.h" + +#define AVATARNAMESPACEDATA "http://www.xmpp.org/extensions/xep-0084.html#ns-data" +#define AVATARNAMESPACEMETA "http://www.xmpp.org/extensions/xep-0084.html#ns-metadata" + typedef struct _JabberBuddy { GList *resources; char *error_msg; @@ -53,6 +57,12 @@ } subscription; } JabberBuddy; +typedef struct _JabberAdHocCommands { + char *jid; + char *node; + char *name; +} JabberAdHocCommands; + typedef struct _JabberBuddyResource { JabberBuddy *jb; char *name; @@ -71,6 +81,8 @@ char *name; char *os; } client; + JabberCapsClientInfo *caps; + GList *commands; } JabberBuddyResource; void jabber_buddy_free(JabberBuddy *jb); @@ -92,6 +104,7 @@ void jabber_set_info(PurpleConnection *gc, const char *info); void jabber_setup_set_info(PurplePluginAction *action); void jabber_set_buddy_icon(PurpleConnection *gc, PurpleStoredImage *img); +void jabber_buddy_avatar_update_metadata(JabberStream *js, const char *from, xmlnode *items); const char *jabber_buddy_state_get_name(JabberBuddyState state); const char *jabber_buddy_state_get_status_id(JabberBuddyState state); @@ -99,6 +112,7 @@ JabberBuddyState jabber_buddy_status_id_get_state(const char *id); JabberBuddyState jabber_buddy_show_get_state(const char *id); +void jabber_user_search(JabberStream *js, const char *directory); void jabber_user_search_begin(PurplePluginAction *); void jabber_buddy_remove_all_pending_buddy_info_requests(JabberStream *js);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/caps.c Wed Sep 12 19:11:38 2007 +0000 @@ -0,0 +1,557 @@ +/* + * purple - Jabber Protocol Plugin + * + * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "internal.h" + +#include "caps.h" +#include <string.h> +#include "internal.h" +#include "util.h" +#include "iq.h" + +#define JABBER_CAPS_FILENAME "xmpp-caps.xml" + +static GHashTable *capstable = NULL; /* JabberCapsKey -> JabberCapsValue */ + +typedef struct _JabberCapsKey { + char *node; + char *ver; +} JabberCapsKey; + +typedef struct _JabberCapsValueExt { + GList *identities; /* JabberCapsIdentity */ + GList *features; /* char * */ +} JabberCapsValueExt; + +typedef struct _JabberCapsValue { + GList *identities; /* JabberCapsIdentity */ + GList *features; /* char * */ + GHashTable *ext; /* char * -> JabberCapsValueExt */ +} JabberCapsValue; + +static guint jabber_caps_hash(gconstpointer key) { + const JabberCapsKey *name = key; + guint nodehash = g_str_hash(name->node); + guint verhash = g_str_hash(name->ver); + + return nodehash ^ verhash; +} + +static gboolean jabber_caps_compare(gconstpointer v1, gconstpointer v2) { + const JabberCapsKey *name1 = v1; + const JabberCapsKey *name2 = v2; + + return strcmp(name1->node,name2->node) == 0 && strcmp(name1->ver,name2->ver) == 0; +} + +static void jabber_caps_destroy_key(gpointer key) { + JabberCapsKey *keystruct = key; + g_free(keystruct->node); + g_free(keystruct->ver); + g_free(keystruct); +} + +static void jabber_caps_destroy_value(gpointer value) { + JabberCapsValue *valuestruct = value; + while(valuestruct->identities) { + JabberCapsIdentity *id = valuestruct->identities->data; + g_free(id->category); + g_free(id->type); + g_free(id->name); + g_free(id); + + valuestruct->identities = g_list_delete_link(valuestruct->identities,valuestruct->identities); + } + while(valuestruct->features) { + g_free(valuestruct->features->data); + valuestruct->features = g_list_delete_link(valuestruct->features,valuestruct->features); + } + g_hash_table_destroy(valuestruct->ext); + g_free(valuestruct); +} + +static void jabber_caps_ext_destroy_value(gpointer value) { + JabberCapsValueExt *valuestruct = value; + while(valuestruct->identities) { + JabberCapsIdentity *id = valuestruct->identities->data; + g_free(id->category); + g_free(id->type); + g_free(id->name); + g_free(id); + + valuestruct->identities = g_list_delete_link(valuestruct->identities,valuestruct->identities); + } + while(valuestruct->features) { + g_free(valuestruct->features->data); + valuestruct->features = g_list_delete_link(valuestruct->features,valuestruct->features); + } + g_free(valuestruct); +} + +static void jabber_caps_load(void); + +void jabber_caps_init(void) { + capstable = g_hash_table_new_full(jabber_caps_hash, jabber_caps_compare, jabber_caps_destroy_key, jabber_caps_destroy_value); + jabber_caps_load(); +} + +static void jabber_caps_load(void) { + xmlnode *capsdata = purple_util_read_xml_from_file(JABBER_CAPS_FILENAME, "XMPP capabilities cache"); + xmlnode *client; + + if(!capsdata) + return; + + if (strcmp(capsdata->name, "capabilities") != 0) { + xmlnode_free(capsdata); + return; + } + + for(client = capsdata->child; client; client = client->next) { + if(client->type != XMLNODE_TYPE_TAG) + continue; + if(!strcmp(client->name, "client")) { + JabberCapsKey *key = g_new0(JabberCapsKey, 1); + JabberCapsValue *value = g_new0(JabberCapsValue, 1); + xmlnode *child; + key->node = g_strdup(xmlnode_get_attrib(client,"node")); + key->ver = g_strdup(xmlnode_get_attrib(client,"ver")); + value->ext = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, jabber_caps_ext_destroy_value); + for(child = client->child; child; child = child->next) { + if(child->type != XMLNODE_TYPE_TAG) + continue; + if(!strcmp(child->name,"feature")) { + const char *var = xmlnode_get_attrib(child, "var"); + if(!var) + continue; + value->features = g_list_append(value->features,g_strdup(var)); + } else if(!strcmp(child->name,"identity")) { + const char *category = xmlnode_get_attrib(child, "category"); + const char *type = xmlnode_get_attrib(child, "type"); + const char *name = xmlnode_get_attrib(child, "name"); + + JabberCapsIdentity *id = g_new0(JabberCapsIdentity, 1); + id->category = g_strdup(category); + id->type = g_strdup(type); + id->name = g_strdup(name); + + value->identities = g_list_append(value->identities,id); + } else if(!strcmp(child->name,"ext")) { + const char *identifier = xmlnode_get_attrib(child, "identifier"); + if(identifier) { + xmlnode *extchild; + + JabberCapsValueExt *extvalue = g_new0(JabberCapsValueExt, 1); + + for(extchild = child->child; extchild; extchild = extchild->next) { + if(extchild->type != XMLNODE_TYPE_TAG) + continue; + if(!strcmp(extchild->name,"feature")) { + const char *var = xmlnode_get_attrib(extchild, "var"); + if(!var) + continue; + extvalue->features = g_list_append(extvalue->features,g_strdup(var)); + } else if(!strcmp(extchild->name,"identity")) { + const char *category = xmlnode_get_attrib(extchild, "category"); + const char *type = xmlnode_get_attrib(extchild, "type"); + const char *name = xmlnode_get_attrib(extchild, "name"); + + JabberCapsIdentity *id = g_new0(JabberCapsIdentity, 1); + id->category = g_strdup(category); + id->type = g_strdup(type); + id->name = g_strdup(name); + + extvalue->identities = g_list_append(extvalue->identities,id); + } + } + g_hash_table_replace(value->ext, g_strdup(identifier), extvalue); + } + } + } + g_hash_table_replace(capstable, key, value); + } + } + xmlnode_free(capsdata); +} + +static void jabber_caps_store_ext(gpointer key, gpointer value, gpointer user_data) { + const char *extname = key; + JabberCapsValueExt *props = value; + xmlnode *root = user_data; + xmlnode *ext = xmlnode_new_child(root,"ext"); + GList *iter; + + xmlnode_set_attrib(ext,"identifier",extname); + + for(iter = props->identities; iter; iter = g_list_next(iter)) { + JabberCapsIdentity *id = iter->data; + xmlnode *identity = xmlnode_new_child(ext, "identity"); + xmlnode_set_attrib(identity, "category", id->category); + xmlnode_set_attrib(identity, "type", id->type); + if (id->name) + xmlnode_set_attrib(identity, "name", id->name); + } + + for(iter = props->features; iter; iter = g_list_next(iter)) { + const char *feat = iter->data; + xmlnode *feature = xmlnode_new_child(ext, "feature"); + xmlnode_set_attrib(feature, "var", feat); + } +} + +static void jabber_caps_store_client(gpointer key, gpointer value, gpointer user_data) { + JabberCapsKey *clientinfo = key; + JabberCapsValue *props = value; + xmlnode *root = user_data; + xmlnode *client = xmlnode_new_child(root,"client"); + GList *iter; + + xmlnode_set_attrib(client,"node",clientinfo->node); + xmlnode_set_attrib(client,"ver",clientinfo->ver); + + for(iter = props->identities; iter; iter = g_list_next(iter)) { + JabberCapsIdentity *id = iter->data; + xmlnode *identity = xmlnode_new_child(client, "identity"); + xmlnode_set_attrib(identity, "category", id->category); + xmlnode_set_attrib(identity, "type", id->type); + xmlnode_set_attrib(identity, "name", id->name); + } + + for(iter = props->features; iter; iter = g_list_next(iter)) { + const char *feat = iter->data; + xmlnode *feature = xmlnode_new_child(client, "feature"); + xmlnode_set_attrib(feature, "var", feat); + } + + g_hash_table_foreach(props->ext,jabber_caps_store_ext,client); +} + +static void jabber_caps_store(void) { + char *str; + xmlnode *root = xmlnode_new("capabilities"); + g_hash_table_foreach(capstable, jabber_caps_store_client, root); + str = xmlnode_to_formatted_str(root, NULL); + xmlnode_free(root); + purple_util_write_data_to_file(JABBER_CAPS_FILENAME, str, -1); + g_free(str); +} + +/* this function assumes that all information is available locally */ +static JabberCapsClientInfo *jabber_caps_collect_info(const char *node, const char *ver, GList *ext) { + JabberCapsClientInfo *result = g_new0(JabberCapsClientInfo, 1); + JabberCapsKey *key = g_new0(JabberCapsKey, 1); + JabberCapsValue *caps; + GList *iter; + + key->node = (char *)node; + key->ver = (char *)ver; + + caps = g_hash_table_lookup(capstable,key); + + g_free(key); + + /* join all information */ + for(iter = caps->identities; iter; iter = g_list_next(iter)) { + JabberCapsIdentity *id = iter->data; + JabberCapsIdentity *newid = g_new0(JabberCapsIdentity, 1); + newid->category = g_strdup(id->category); + newid->type = g_strdup(id->type); + newid->name = g_strdup(id->name); + + result->identities = g_list_append(result->identities,newid); + } + for(iter = caps->features; iter; iter = g_list_next(iter)) { + const char *feat = iter->data; + char *newfeat = g_strdup(feat); + + result->features = g_list_append(result->features,newfeat); + } + + for(iter = ext; iter; iter = g_list_next(iter)) { + const char *extname = iter->data; + JabberCapsValueExt *extinfo = g_hash_table_lookup(caps->ext,extname); + + if(extinfo) { + GList *iter2; + for(iter2 = extinfo->identities; iter2; iter2 = g_list_next(iter2)) { + JabberCapsIdentity *id = iter2->data; + JabberCapsIdentity *newid = g_new0(JabberCapsIdentity, 1); + newid->category = g_strdup(id->category); + newid->type = g_strdup(id->type); + newid->name = g_strdup(id->name); + + result->identities = g_list_append(result->identities,newid); + } + for(iter2 = extinfo->features; iter2; iter2 = g_list_next(iter2)) { + const char *feat = iter2->data; + char *newfeat = g_strdup(feat); + + result->features = g_list_append(result->features,newfeat); + } + } + } + return result; +} + +void jabber_caps_free_clientinfo(JabberCapsClientInfo *clientinfo) { + if(!clientinfo) + return; + while(clientinfo->identities) { + JabberCapsIdentity *id = clientinfo->identities->data; + g_free(id->category); + g_free(id->type); + g_free(id->name); + g_free(id); + + clientinfo->identities = g_list_delete_link(clientinfo->identities,clientinfo->identities); + } + while(clientinfo->features) { + char *feat = clientinfo->features->data; + g_free(feat); + + clientinfo->features = g_list_delete_link(clientinfo->features,clientinfo->features); + } + + g_free(clientinfo); +} + +typedef struct _jabber_caps_cbplususerdata { + jabber_caps_get_info_cb cb; + gpointer user_data; + + char *who; + char *node; + char *ver; + GList *ext; + unsigned extOutstanding; +} jabber_caps_cbplususerdata; + +typedef struct jabber_ext_userdata { + jabber_caps_cbplususerdata *userdata; + char *node; +} jabber_ext_userdata; + +static void jabber_caps_get_info_check_completion(jabber_caps_cbplususerdata *userdata) { + if(userdata->extOutstanding == 0) { + userdata->cb(jabber_caps_collect_info(userdata->node, userdata->ver, userdata->ext), userdata->user_data); + g_free(userdata->who); + g_free(userdata->node); + g_free(userdata->ver); + while(userdata->ext) { + g_free(userdata->ext->data); + userdata->ext = g_list_delete_link(userdata->ext,userdata->ext); + } + g_free(userdata); + } +} + +static void jabber_caps_ext_iqcb(JabberStream *js, xmlnode *packet, gpointer data) { + /* collect data and fetch all exts */ + xmlnode *query = xmlnode_get_child_with_namespace(packet,"query","http://jabber.org/protocol/disco#info"); + xmlnode *child; + jabber_ext_userdata *extuserdata = data; + jabber_caps_cbplususerdata *userdata = extuserdata->userdata; + JabberCapsValue *client; + const char *node = extuserdata->node; + const char *key; + + --userdata->extOutstanding; + + if(node) { + JabberCapsValueExt *value = g_new0(JabberCapsValueExt, 1); + + JabberCapsKey *clientkey = g_new0(JabberCapsKey, 1); + clientkey->node = userdata->node; + clientkey->ver = userdata->ver; + + client = g_hash_table_lookup(capstable,clientkey); + + g_free(clientkey); + + /* split node by #, key either points to \0 or the correct ext afterwards */ + for(key = node; key[0] != '\0'; ++key) { + if(key[0] == '#') { + ++key; + break; + } + } + + for(child = query->child; child; child = child->next) { + if(child->type != XMLNODE_TYPE_TAG) + continue; + if(!strcmp(child->name,"feature")) { + const char *var = xmlnode_get_attrib(child, "var"); + if(!var) + continue; + value->features = g_list_append(value->features,g_strdup(var)); + } else if(!strcmp(child->name,"identity")) { + const char *category = xmlnode_get_attrib(child, "category"); + const char *type = xmlnode_get_attrib(child, "type"); + const char *name = xmlnode_get_attrib(child, "name"); + + JabberCapsIdentity *id = g_new0(JabberCapsIdentity, 1); + id->category = g_strdup(category); + id->type = g_strdup(type); + id->name = g_strdup(name); + + value->identities = g_list_append(value->identities,id); + } + } + g_hash_table_replace(client->ext, g_strdup(key), value); + + jabber_caps_store(); + } + g_free(extuserdata->node); + g_free(extuserdata); + jabber_caps_get_info_check_completion(userdata); +} + +static void jabber_caps_client_iqcb(JabberStream *js, xmlnode *packet, gpointer data) { + /* collect data and fetch all exts */ + xmlnode *query = xmlnode_get_child_with_namespace(packet,"query","http://jabber.org/protocol/disco#info"); + xmlnode *child; + GList *iter; + jabber_caps_cbplususerdata *userdata = data; + JabberCapsKey *key = g_new0(JabberCapsKey, 1); + JabberCapsValue *value = g_new0(JabberCapsValue, 1); + key->node = g_strdup(userdata->node); + key->ver = g_strdup(userdata->ver); + + value->ext = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, jabber_caps_ext_destroy_value); + + for(child = query->child; child; child = child->next) { + if(child->type != XMLNODE_TYPE_TAG) + continue; + if(!strcmp(child->name,"feature")) { + const char *var = xmlnode_get_attrib(child, "var"); + if(!var) + continue; + value->features = g_list_append(value->features,g_strdup(var)); + } else if(!strcmp(child->name,"identity")) { + const char *category = xmlnode_get_attrib(child, "category"); + const char *type = xmlnode_get_attrib(child, "type"); + const char *name = xmlnode_get_attrib(child, "name"); + + JabberCapsIdentity *id = g_new0(JabberCapsIdentity, 1); + id->category = g_strdup(category); + id->type = g_strdup(type); + id->name = g_strdup(name); + + value->identities = g_list_append(value->identities,id); + } + } + g_hash_table_replace(capstable, key, value); + + /* fetch all exts */ + for(iter = userdata->ext; iter; iter = g_list_next(iter)) { + JabberIq *iq = jabber_iq_new_query(js,JABBER_IQ_GET,"http://jabber.org/protocol/disco#info"); + xmlnode *query = xmlnode_get_child_with_namespace(iq->node,"query","http://jabber.org/protocol/disco#info"); + char *node = g_strdup_printf("%s#%s", userdata->node, (const char*)iter->data); + jabber_ext_userdata *ext_data = g_new0(jabber_ext_userdata, 1); + ext_data->node = node; + ext_data->userdata = userdata; + + xmlnode_set_attrib(query, "node", node); + xmlnode_set_attrib(iq->node, "to", userdata->who); + + jabber_iq_set_callback(iq,jabber_caps_ext_iqcb,ext_data); + jabber_iq_send(iq); + } + + jabber_caps_store(); + + jabber_caps_get_info_check_completion(userdata); +} + +void jabber_caps_get_info(JabberStream *js, const char *who, const char *node, const char *ver, const char *ext, jabber_caps_get_info_cb cb, gpointer user_data) { + JabberCapsValue *client; + JabberCapsKey *key = g_new0(JabberCapsKey, 1); + char *originalext = g_strdup(ext); + jabber_caps_cbplususerdata *userdata = g_new0(jabber_caps_cbplususerdata, 1); + userdata->cb = cb; + userdata->user_data = user_data; + userdata->who = g_strdup(who); + userdata->node = g_strdup(node); + userdata->ver = g_strdup(ver); + + if(originalext) { + int i; + gchar **splat = g_strsplit(originalext, " ", 0); + for(i =0; splat[i]; i++) { + userdata->ext = g_list_append(userdata->ext, splat[i]); + ++userdata->extOutstanding; + } + g_free(splat); + } + g_free(originalext); + + key->node = (char *)node; + key->ver = (char *)ver; + + client = g_hash_table_lookup(capstable, key); + + g_free(key); + + if(!client) { + JabberIq *iq = jabber_iq_new_query(js,JABBER_IQ_GET,"http://jabber.org/protocol/disco#info"); + xmlnode *query = xmlnode_get_child_with_namespace(iq->node,"query","http://jabber.org/protocol/disco#info"); + char *nodever = g_strdup_printf("%s#%s", node, ver); + xmlnode_set_attrib(query, "node", nodever); + g_free(nodever); + xmlnode_set_attrib(iq->node, "to", who); + + jabber_iq_set_callback(iq,jabber_caps_client_iqcb,userdata); + jabber_iq_send(iq); + } else { + GList *iter; + /* fetch unknown exts only */ + for(iter = userdata->ext; iter; iter = g_list_next(iter)) { + JabberCapsValueExt *extvalue = g_hash_table_lookup(client->ext, (const char*)iter->data); + JabberIq *iq; + xmlnode *query; + char *nodever; + jabber_ext_userdata *ext_data; + + if(extvalue) { + /* we already have this ext, don't bother with it */ + --userdata->extOutstanding; + continue; + } + + ext_data = g_new0(jabber_ext_userdata, 1); + + iq = jabber_iq_new_query(js,JABBER_IQ_GET,"http://jabber.org/protocol/disco#info"); + query = xmlnode_get_child_with_namespace(iq->node,"query","http://jabber.org/protocol/disco#info"); + nodever = g_strdup_printf("%s#%s", node, (const char*)iter->data); + xmlnode_set_attrib(query, "node", nodever); + xmlnode_set_attrib(iq->node, "to", who); + + ext_data->node = nodever; + ext_data->userdata = userdata; + + jabber_iq_set_callback(iq, jabber_caps_ext_iqcb, ext_data); + jabber_iq_send(iq); + } + /* maybe we have all data available anyways? This is the ideal case where no network traffic is necessary */ + jabber_caps_get_info_check_completion(userdata); + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/caps.h Wed Sep 12 19:11:38 2007 +0000 @@ -0,0 +1,49 @@ +/* + * purple - Jabber Protocol Plugin + * + * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _PURPLE_JABBER_CAPS_H_ +#define _PURPLE_JABBER_CAPS_H_ + +typedef struct _JabberCapsClientInfo JabberCapsClientInfo; + +#include "jabber.h" + +/* Implementation of XEP-0115 */ + +typedef struct _JabberCapsIdentity { + char *category; + char *type; + char *name; +} JabberCapsIdentity; + +struct _JabberCapsClientInfo { + GList *identities; /* JabberCapsIdentity */ + GList *features; /* char * */ +}; + +typedef void (*jabber_caps_get_info_cb)(JabberCapsClientInfo *info, gpointer user_data); + +void jabber_caps_init(void); + +void jabber_caps_get_info(JabberStream *js, const char *who, const char *node, const char *ver, const char *ext, jabber_caps_get_info_cb cb, gpointer user_data); +void jabber_caps_free_clientinfo(JabberCapsClientInfo *clientinfo); + +#endif /* _PURPLE_JABBER_CAPS_H_ */
--- a/libpurple/protocols/jabber/chat.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/jabber/chat.c Wed Sep 12 19:11:38 2007 +0000 @@ -15,7 +15,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */ #include "internal.h" @@ -261,7 +261,7 @@ purple_status_to_jabber(status, &state, &msg, &priority); - presence = jabber_presence_create(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); @@ -634,7 +634,7 @@ purple_status_to_jabber(status, &state, &msg, &priority); - presence = jabber_presence_create(state, msg, priority); + presence = jabber_presence_create_js(chat->js, state, msg, priority); full_jid = g_strdup_printf("%s@%s/%s", chat->room, chat->server, nick); xmlnode_set_attrib(presence, "to", full_jid); g_free(full_jid); @@ -833,12 +833,18 @@ gboolean jabber_chat_ban_user(JabberChat *chat, const char *who, const char *why) { + JabberChatMember *jcm; + const char *jid; + char *to; JabberIq *iq; - JabberChatMember *jcm = g_hash_table_lookup(chat->members, who); - char *to; xmlnode *query, *item, *reason; - if(!jcm || !jcm->jid) + jcm = g_hash_table_lookup(chat->members, who); + if (jcm && jcm->jid) + jid = jcm->jid; + else if (g_utf8_strchr(who, -1, '@') != NULL) + jid = who; + else return FALSE; iq = jabber_iq_new_query(chat->js, JABBER_IQ_SET, @@ -850,7 +856,7 @@ query = xmlnode_get_child(iq->node, "query"); item = xmlnode_new_child(query, "item"); - xmlnode_set_attrib(item, "jid", jcm->jid); + xmlnode_set_attrib(item, "jid", jid); xmlnode_set_attrib(item, "affiliation", "outcast"); if(why) { reason = xmlnode_new_child(item, "reason"); @@ -864,14 +870,18 @@ gboolean jabber_chat_affiliate_user(JabberChat *chat, const char *who, const char *affiliation) { + JabberChatMember *jcm; + const char *jid; char *to; JabberIq *iq; xmlnode *query, *item; - JabberChatMember *jcm; jcm = g_hash_table_lookup(chat->members, who); - - if (!jcm || !jcm->jid) + if (jcm && jcm->jid) + jid = jcm->jid; + else if (g_utf8_strchr(who, -1, '@') != NULL) + jid = who; + else return FALSE; iq = jabber_iq_new_query(chat->js, JABBER_IQ_SET, @@ -883,7 +893,7 @@ query = xmlnode_get_child(iq->node, "query"); item = xmlnode_new_child(query, "item"); - xmlnode_set_attrib(item, "jid", jcm->jid); + xmlnode_set_attrib(item, "jid", jid); xmlnode_set_attrib(item, "affiliation", affiliation); jabber_iq_send(iq);
--- a/libpurple/protocols/jabber/chat.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/jabber/chat.h Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _PURPLE_JABBER_CHAT_H_ #define _PURPLE_JABBER_CHAT_H_
--- a/libpurple/protocols/jabber/disco.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/jabber/disco.c Wed Sep 12 19:11:38 2007 +0000 @@ -15,7 +15,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */ @@ -30,15 +30,19 @@ #include "jabber.h" #include "presence.h" #include "roster.h" +#include "pep.h" +#include "adhoccommands.h" + struct _jabber_disco_info_cb_data { gpointer data; JabberDiscoInfoCallback *callback; }; -#define SUPPORT_FEATURE(x) \ +#define SUPPORT_FEATURE(x) { \ feature = xmlnode_new_child(query, "feature"); \ - xmlnode_set_attrib(feature, "var", x); + xmlnode_set_attrib(feature, "var", x); \ +} void jabber_disco_info_parse(JabberStream *js, xmlnode *packet) { @@ -72,7 +76,6 @@ xmlnode_set_attrib(query, "node", node); if(!node || !strcmp(node, CAPS0115_NODE "#" VERSION)) { - identity = xmlnode_new_child(query, "identity"); xmlnode_set_attrib(identity, "category", "client"); xmlnode_set_attrib(identity, "type", "pc"); /* XXX: bot, console, @@ -98,18 +101,62 @@ SUPPORT_FEATURE("http://jabber.org/protocol/si/profile/file-transfer") SUPPORT_FEATURE("http://jabber.org/protocol/xhtml-im") SUPPORT_FEATURE("urn:xmpp:ping") + SUPPORT_FEATURE("http://www.xmpp.org/extensions/xep-0199.html#ns") + + if(!node) { /* non-caps disco#info, add all enabled extensions */ + GList *features; + for(features = jabber_features; features; features = features->next) { + JabberFeature *feat = (JabberFeature*)features->data; + if(feat->is_enabled == NULL || feat->is_enabled(js, feat->shortname, feat->namespace) == TRUE) + SUPPORT_FEATURE(feat->namespace); + } + } } else { - xmlnode *error, *inf; - - /* XXX: gross hack, implement jabber_iq_set_type or something */ - xmlnode_set_attrib(iq->node, "type", "error"); - iq->type = JABBER_IQ_ERROR; - - error = xmlnode_new_child(query, "error"); - xmlnode_set_attrib(error, "code", "404"); - xmlnode_set_attrib(error, "type", "cancel"); - inf = xmlnode_new_child(error, "item-not-found"); - xmlnode_set_namespace(inf, "urn:ietf:params:xml:ns:xmpp-stanzas"); + const char *ext = NULL; + unsigned pos; + unsigned nodelen = strlen(node); + unsigned capslen = strlen(CAPS0115_NODE); + /* do a basic plausability check */ + if(nodelen > capslen+1) { + /* verify that the string is CAPS0115#<ext> and get the pointer to the ext part */ + for(pos = 0; pos < capslen+1; ++pos) { + if(pos == capslen) { + if(node[pos] == '#') + ext = &node[pos+1]; + else + break; + } else if(node[pos] != CAPS0115_NODE[pos]) + break; + } + + if(ext != NULL) { + /* look for that ext */ + GList *features; + for(features = jabber_features; features; features = features->next) { + JabberFeature *feat = (JabberFeature*)features->data; + if(!strcmp(feat->shortname, ext)) { + SUPPORT_FEATURE(feat->namespace); + break; + } + } + if(features == NULL) + ext = NULL; + } + } + + if(ext == NULL) { + xmlnode *error, *inf; + + /* XXX: gross hack, implement jabber_iq_set_type or something */ + xmlnode_set_attrib(iq->node, "type", "error"); + iq->type = JABBER_IQ_ERROR; + + error = xmlnode_new_child(query, "error"); + xmlnode_set_attrib(error, "code", "404"); + xmlnode_set_attrib(error, "type", "cancel"); + inf = xmlnode_new_child(error, "item-not-found"); + xmlnode_set_namespace(inf, "urn:ietf:params:xml:ns:xmpp-stanzas"); + } } jabber_iq_send(iq); @@ -165,6 +212,11 @@ capabilities |= JABBER_CAP_IQ_SEARCH; else if(!strcmp(var, "jabber:iq:register")) capabilities |= JABBER_CAP_IQ_REGISTER; + else if(!strcmp(var, "http://www.xmpp.org/extensions/xep-0199.html#ns")) + capabilities |= JABBER_CAP_PING; + else if(!strcmp(var, "http://jabber.org/protocol/commands")) { + capabilities |= JABBER_CAP_ADHOC; + } } } @@ -208,6 +260,17 @@ if(type && !strcmp(type, "get")) { JabberIq *iq = jabber_iq_new_query(js, JABBER_IQ_RESULT, "http://jabber.org/protocol/disco#items"); + + /* preserve node */ + xmlnode *iq_query = xmlnode_get_child_with_namespace(iq->node,"query","http://jabber.org/protocol/disco#items"); + if(iq_query) { + xmlnode *query = xmlnode_get_child_with_namespace(packet,"query","http://jabber.org/protocol/disco#items"); + if(query) { + const char *node = xmlnode_get_attrib(query,"node"); + if(node) + xmlnode_set_attrib(iq_query,"node",node); + } + } jabber_iq_set_id(iq, xmlnode_get_attrib(packet, "id")); @@ -227,7 +290,13 @@ jabber_roster_request(js); } - /* when we get the roster back, we'll send our initial presence */ + /* Send initial presence; this will trigger receipt of presence for contacts on the roster */ + jabber_presence_send(js->gc->account, NULL); + + if (js->server_caps & JABBER_CAP_ADHOC) { + /* The server supports ad-hoc commands, so let's request the list */ + jabber_adhoc_server_get_list(js); + } } static void @@ -260,9 +329,11 @@ child = xmlnode_get_next_twin(child)) { const char *category, *type, *name; category = xmlnode_get_attrib(child, "category"); + type = xmlnode_get_attrib(child, "type"); + if(category && type && !strcmp(category, "pubsub") && !strcmp(type,"pep")) + js->pep = TRUE; if (!category || strcmp(category, "server")) continue; - type = xmlnode_get_attrib(child, "type"); if (!type || strcmp(type, "im")) continue; @@ -273,7 +344,7 @@ g_free(js->server_name); js->server_name = g_strdup(name); if (!strcmp(name, "Google Talk")) { - purple_debug_info("jabber", "Google Talk!"); + purple_debug_info("jabber", "Google Talk!\n"); js->googletalk = TRUE; } } @@ -291,6 +362,8 @@ } else if (!strcmp("google:roster", var)) { js->server_caps |= JABBER_CAP_GOOGLE_ROSTER; jabber_google_roster_init(js); + } else if (!strcmp("http://jabber.org/protocol/commands", var)) { + js->server_caps |= JABBER_CAP_ADHOC; } }
--- a/libpurple/protocols/jabber/disco.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/jabber/disco.h Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _PURPLE_JABBER_DISCO_H_ #define _PURPLE_JABBER_DISCO_H_
--- a/libpurple/protocols/jabber/google.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/jabber/google.c Wed Sep 12 19:11:38 2007 +0000 @@ -15,7 +15,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "internal.h"
--- a/libpurple/protocols/jabber/google.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/jabber/google.h Wed Sep 12 19:11:38 2007 +0000 @@ -15,7 +15,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _PURPLE_GOOGLE_H_
--- a/libpurple/protocols/jabber/iq.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/jabber/iq.c Wed Sep 12 19:11:38 2007 +0000 @@ -15,7 +15,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */ #include "internal.h" @@ -31,6 +31,8 @@ #include "oob.h" #include "roster.h" #include "si.h" +#include "ping.h" +#include "adhoccommands.h" #ifdef _WIN32 #include "utsname.h" @@ -343,6 +345,13 @@ jabber_gmail_poke(js, packet); return; } + + purple_debug_info("jabber", "jabber_iq_parse\n"); + + if(xmlnode_get_child_with_namespace(packet, "ping", "urn:xmpp:ping")) { + jabber_ping_parse(js, packet); + return; + } /* If we get here, send the default error reply mandated by XMPP-CORE */ if(type && (!strcmp(type, "set") || !strcmp(type, "get"))) {
--- a/libpurple/protocols/jabber/iq.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/jabber/iq.h Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _PURPLE_JABBER_IQ_H_ #define _PURPLE_JABBER_IQ_H_
--- a/libpurple/protocols/jabber/jabber.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/jabber/jabber.c Wed Sep 12 19:11:38 2007 +0000 @@ -15,7 +15,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */ #include "internal.h" @@ -51,12 +51,20 @@ #include "presence.h" #include "jabber.h" #include "roster.h" +#include "ping.h" #include "si.h" #include "xdata.h" +#include "pep.h" +#include "adhoccommands.h" -#define JABBER_CONNECT_STEPS (js->gsc ? 8 : 5) +#include <assert.h> + +#define JABBER_CONNECT_STEPS (js->gsc ? 9 : 5) static PurplePlugin *my_protocol = NULL; +GList *jabber_features; + +static void jabber_unregister_account_cb(JabberStream *js); static void jabber_stream_init(JabberStream *js) { @@ -80,6 +88,8 @@ const char *type = xmlnode_get_attrib(packet, "type"); if(type && !strcmp(type, "result")) { jabber_stream_set_state(js, JABBER_STREAM_CONNECTED); + if(js->unregistration) + jabber_unregister_account_cb(js); } else { purple_connection_error(js->gc, _("Error initializing session")); } @@ -132,6 +142,9 @@ if(xmlnode_get_child(packet, "starttls")) { if(jabber_process_starttls(js, packet)) return; + } else if(purple_account_get_bool(js->gc->account, "require_tls", FALSE) && !js->gsc) { + purple_connection_error(js->gc, _("You require encryption, but it is not available on this server.")); + return; } if(js->registration) { @@ -169,49 +182,49 @@ static void tls_init(JabberStream *js); -void jabber_process_packet(JabberStream *js, xmlnode *packet) +void jabber_process_packet(JabberStream *js, xmlnode **packet) { const char *xmlns; - purple_signal_emit(my_protocol, "jabber-receiving-xmlnode", js->gc, &packet); + purple_signal_emit(my_protocol, "jabber-receiving-xmlnode", js->gc, packet); /* if the signal leaves us with a null packet, we're done */ - if(NULL == packet) + if(NULL == *packet) return; - xmlns = xmlnode_get_namespace(packet); + xmlns = xmlnode_get_namespace(*packet); - if(!strcmp(packet->name, "iq")) { - jabber_iq_parse(js, packet); - } else if(!strcmp(packet->name, "presence")) { - jabber_presence_parse(js, packet); - } else if(!strcmp(packet->name, "message")) { - jabber_message_parse(js, packet); - } else if(!strcmp(packet->name, "stream:features")) { - jabber_stream_features_parse(js, packet); - } else if (!strcmp(packet->name, "features") && + if(!strcmp((*packet)->name, "iq")) { + jabber_iq_parse(js, *packet); + } else if(!strcmp((*packet)->name, "presence")) { + jabber_presence_parse(js, *packet); + } else if(!strcmp((*packet)->name, "message")) { + jabber_message_parse(js, *packet); + } else if(!strcmp((*packet)->name, "stream:features")) { + jabber_stream_features_parse(js, *packet); + } else if (!strcmp((*packet)->name, "features") && !strcmp(xmlns, "http://etherx.jabber.org/streams")) { - jabber_stream_features_parse(js, packet); - } else if(!strcmp(packet->name, "stream:error") || - (!strcmp(packet->name, "error") && + jabber_stream_features_parse(js, *packet); + } else if(!strcmp((*packet)->name, "stream:error") || + (!strcmp((*packet)->name, "error") && !strcmp(xmlns, "http://etherx.jabber.org/streams"))) { - jabber_stream_handle_error(js, packet); - } else if(!strcmp(packet->name, "challenge")) { + jabber_stream_handle_error(js, *packet); + } else if(!strcmp((*packet)->name, "challenge")) { if(js->state == JABBER_STREAM_AUTHENTICATING) - jabber_auth_handle_challenge(js, packet); - } else if(!strcmp(packet->name, "success")) { + jabber_auth_handle_challenge(js, *packet); + } else if(!strcmp((*packet)->name, "success")) { if(js->state == JABBER_STREAM_AUTHENTICATING) - jabber_auth_handle_success(js, packet); - } else if(!strcmp(packet->name, "failure")) { + jabber_auth_handle_success(js, *packet); + } else if(!strcmp((*packet)->name, "failure")) { if(js->state == JABBER_STREAM_AUTHENTICATING) - jabber_auth_handle_failure(js, packet); - } else if(!strcmp(packet->name, "proceed")) { + jabber_auth_handle_failure(js, *packet); + } else if(!strcmp((*packet)->name, "proceed")) { if(js->state == JABBER_STREAM_AUTHENTICATING && !js->gsc) tls_init(js); } else { purple_debug(PURPLE_DEBUG_WARNING, "jabber", "Unknown packet: %s\n", - packet->name); + (*packet)->name); } } @@ -453,6 +466,9 @@ jabber_send_raw(js, "<?xml version='1.0' ?>", -1); jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING); purple_ssl_input_add(gsc, jabber_recv_cb_ssl, gc); + + /* Tell the app that we're doing encryption */ + jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING_ENCRYPTION); } @@ -494,29 +510,20 @@ js = gc->proto_data; js->gsc = NULL; - switch(error) { - case PURPLE_SSL_CONNECT_FAILED: - purple_connection_error(gc, _("Connection Failed")); - break; - case PURPLE_SSL_HANDSHAKE_FAILED: - purple_connection_error(gc, _("SSL Handshake Failed")); - break; - } + purple_connection_error(gc, purple_ssl_strerror(error)); } static void tls_init(JabberStream *js) { purple_input_remove(js->gc->inpa); js->gc->inpa = 0; - js->gsc = purple_ssl_connect_fd(js->gc->account, js->fd, - jabber_login_callback_ssl, jabber_ssl_connect_failure, js->gc); + js->gsc = purple_ssl_connect_with_host_fd(js->gc->account, js->fd, + jabber_login_callback_ssl, jabber_ssl_connect_failure, js->serverFQDN, js->gc); } static void jabber_login_connect(JabberStream *js, const char *fqdn, const char *host, int port) { -#ifdef HAVE_CYRUS_SASL js->serverFQDN = g_strdup(fqdn); -#endif if (purple_proxy_connect(js->gc, js->gc->account, host, port, jabber_login_callback, js->gc) == NULL) @@ -563,6 +570,7 @@ js->user = jabber_id_new(purple_account_get_username(account)); js->next_id = g_random_int(); js->write_buffer = purple_circ_buffer_new(512); + js->old_length = -1; if(!js->user) { purple_connection_error(gc, _("Invalid XMPP ID")); @@ -623,6 +631,8 @@ JabberStream *js = data; PurpleAccount *account = purple_connection_get_account(js->gc); + jabber_parser_free(js); + purple_account_disconnect(account); return FALSE; @@ -637,12 +647,21 @@ static void jabber_registration_result_cb(JabberStream *js, xmlnode *packet, gpointer data) { + PurpleAccount *account = purple_connection_get_account(js->gc); const char *type = xmlnode_get_attrib(packet, "type"); char *buf; + char *to = data; if(!strcmp(type, "result")) { + if(js->registration) { buf = g_strdup_printf(_("Registration of %s@%s successful"), js->user->node, js->user->domain); + if(account->registration_cb) + (account->registration_cb)(account, TRUE, account->registration_cb_user_data); + } + else + buf = g_strdup_printf(_("Registration to %s successful"), + to); purple_notify_info(NULL, _("Registration Successful"), _("Registration Successful"), buf); g_free(buf); @@ -655,20 +674,56 @@ purple_notify_error(NULL, _("Registration Failed"), _("Registration Failed"), msg); g_free(msg); + if(account->registration_cb) + (account->registration_cb)(account, FALSE, account->registration_cb_user_data); } + g_free(to); + if(js->registration) jabber_connection_schedule_close(js); } static void -jabber_register_cb(JabberStream *js, PurpleRequestFields *fields) +jabber_unregistration_result_cb(JabberStream *js, xmlnode *packet, gpointer data) +{ + const char *type = xmlnode_get_attrib(packet, "type"); + char *buf; + char *to = data; + + if(!strcmp(type, "result")) { + buf = g_strdup_printf(_("Registration from %s successfully removed"), + to); + purple_notify_info(NULL, _("Unregistration Successful"), + _("Unregistration Successful"), buf); + g_free(buf); + } else { + char *msg = jabber_parse_error(js, packet); + + if(!msg) + msg = g_strdup(_("Unknown Error")); + + purple_notify_error(NULL, _("Unregistration Failed"), + _("Unregistration Failed"), msg); + g_free(msg); + } + g_free(to); +} + +typedef struct _JabberRegisterCBData { + JabberStream *js; + char *who; +} JabberRegisterCBData; + +static void +jabber_register_cb(JabberRegisterCBData *cbdata, PurpleRequestFields *fields) { GList *groups, *flds; xmlnode *query, *y; JabberIq *iq; char *username; - iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:register"); + iq = jabber_iq_new_query(cbdata->js, JABBER_IQ_SET, "jabber:iq:register"); query = xmlnode_get_child(iq->node, "query"); + xmlnode_set_attrib(iq->node,"to",cbdata->who); for(groups = purple_request_fields_get_groups(fields); groups; groups = groups->next) { @@ -676,6 +731,24 @@ flds; flds = flds->next) { PurpleRequestField *field = flds->data; const char *id = purple_request_field_get_id(field); + if(!strcmp(id,"unregister")) { + gboolean value = purple_request_field_bool_get_value(field); + if(value) { + /* unregister from service. this doesn't include any of the fields, so remove them from the stanza by recreating it + (there's no "remove child" function for xmlnode) */ + jabber_iq_free(iq); + iq = jabber_iq_new_query(cbdata->js, JABBER_IQ_SET, "jabber:iq:register"); + query = xmlnode_get_child(iq->node, "query"); + xmlnode_set_attrib(iq->node,"to",cbdata->who); + xmlnode_new_child(query, "remove"); + + jabber_iq_set_callback(iq, jabber_unregistration_result_cb, cbdata->who); + + jabber_iq_send(iq); + g_free(cbdata); + return; + } + } else { const char *value = purple_request_field_string_get_value(field); if(!strcmp(id, "username")) { @@ -710,73 +783,97 @@ continue; } xmlnode_insert_data(y, value, -1); - if(!strcmp(id, "username")) { - if(js->user->node) - g_free(js->user->node); - js->user->node = g_strdup(value); + if(cbdata->js->registration && !strcmp(id, "username")) { + if(cbdata->js->user->node) + 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); } } + } - username = g_strdup_printf("%s@%s/%s", js->user->node, js->user->domain, - js->user->resource); - purple_account_set_username(js->gc->account, username); + if(cbdata->js->registration) { + username = g_strdup_printf("%s@%s/%s", cbdata->js->user->node, cbdata->js->user->domain, + cbdata->js->user->resource); + purple_account_set_username(cbdata->js->gc->account, username); g_free(username); + } - jabber_iq_set_callback(iq, jabber_registration_result_cb, NULL); + jabber_iq_set_callback(iq, jabber_registration_result_cb, cbdata->who); jabber_iq_send(iq); - + g_free(cbdata); } static void -jabber_register_cancel_cb(JabberStream *js, PurpleRequestFields *fields) +jabber_register_cancel_cb(JabberRegisterCBData *cbdata, PurpleRequestFields *fields) { - jabber_connection_schedule_close(js); + PurpleAccount *account = purple_connection_get_account(cbdata->js->gc); + if(account && cbdata->js->registration) { + if(account->registration_cb) + (account->registration_cb)(account, FALSE, account->registration_cb_user_data); + jabber_connection_schedule_close(cbdata->js); +} + g_free(cbdata->who); + g_free(cbdata); } static void jabber_register_x_data_cb(JabberStream *js, xmlnode *result, gpointer data) { xmlnode *query; JabberIq *iq; + char *to = data; iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:register"); query = xmlnode_get_child(iq->node, "query"); + xmlnode_set_attrib(iq->node,"to",to); xmlnode_insert_child(query, result); - jabber_iq_set_callback(iq, jabber_registration_result_cb, NULL); + jabber_iq_set_callback(iq, jabber_registration_result_cb, to); jabber_iq_send(iq); } void jabber_register_parse(JabberStream *js, xmlnode *packet) { + PurpleAccount *account = purple_connection_get_account(js->gc); const char *type; - if(!(type = xmlnode_get_attrib(packet, "type")) || strcmp(type, "result")) - return; - - if(js->registration) { + const char *from = xmlnode_get_attrib(packet, "from"); PurpleRequestFields *fields; PurpleRequestFieldGroup *group; PurpleRequestField *field; xmlnode *query, *x, *y; char *instructions; + JabberRegisterCBData *cbdata; + gboolean registered = FALSE; + if(!(type = xmlnode_get_attrib(packet, "type")) || strcmp(type, "result")) + return; + + if(js->registration) /* get rid of the login thingy */ purple_connection_set_state(js->gc, PURPLE_CONNECTED); query = xmlnode_get_child(packet, "query"); if(xmlnode_get_child(query, "registered")) { + registered = TRUE; + + if(js->registration) { purple_notify_error(NULL, _("Already Registered"), _("Already Registered"), NULL); + if(account->registration_cb) + (account->registration_cb)(account, FALSE, account->registration_cb_user_data); jabber_connection_schedule_close(js); return; } + } if((x = xmlnode_get_child_with_namespace(packet, "x", "jabber:x:data"))) { - jabber_x_data_request(js, x, jabber_register_x_data_cb, NULL); + jabber_x_data_request(js, x, jabber_register_x_data_cb, g_strdup(from)); return; } else if((x = xmlnode_get_child_with_namespace(packet, "x", "jabber:x:oob"))) { @@ -787,8 +884,12 @@ if((href = xmlnode_get_data(url))) { purple_notify_uri(NULL, href); g_free(href); + if(js->registration) { js->gc->wants_to_die = TRUE; + if(account->registration_cb) /* succeeded, but we have no login info */ + (account->registration_cb)(account, TRUE, account->registration_cb_user_data); jabber_connection_schedule_close(js); + } return; } } @@ -800,18 +901,32 @@ group = purple_request_field_group_new(NULL); purple_request_fields_add_group(fields, group); + if(js->registration) field = purple_request_field_string_new("username", _("Username"), js->user->node, FALSE); + else + field = purple_request_field_string_new("username", _("Username"), + NULL, FALSE); + purple_request_field_group_add_field(group, field); + if(js->registration) field = purple_request_field_string_new("password", _("Password"), purple_connection_get_password(js->gc), FALSE); + else + field = purple_request_field_string_new("password", _("Password"), + NULL, FALSE); + purple_request_field_string_set_masked(field, TRUE); purple_request_field_group_add_field(group, field); if(xmlnode_get_child(query, "name")) { + if(js->registration) field = purple_request_field_string_new("name", _("Name"), purple_account_get_alias(js->gc->account), FALSE); + else + field = purple_request_field_string_new("name", _("Name"), + NULL, FALSE); purple_request_field_group_add_field(group, field); } if(xmlnode_get_child(query, "email")) { @@ -869,23 +984,45 @@ NULL, FALSE); purple_request_field_group_add_field(group, field); } + if(registered) { + field = purple_request_field_bool_new("unregister", _("Unregister"), FALSE); + purple_request_field_group_add_field(group, field); + } if((y = xmlnode_get_child(query, "instructions"))) instructions = xmlnode_get_data(y); + else if(registered) + instructions = g_strdup(_("Please fill out the information below " + "to change your account registration.")); else instructions = g_strdup(_("Please fill out the information below " "to register your new account.")); + cbdata = g_new0(JabberRegisterCBData, 1); + cbdata->js = js; + cbdata->who = g_strdup(from); + + if(js->registration) purple_request_fields(js->gc, _("Register New XMPP Account"), _("Register New XMPP Account"), instructions, fields, _("Register"), G_CALLBACK(jabber_register_cb), _("Cancel"), G_CALLBACK(jabber_register_cancel_cb), purple_connection_get_account(js->gc), NULL, NULL, - "register-account", js); + "register-account", cbdata); + else { + char *title = registered?g_strdup_printf(_("Change Account Registration at %s"), from) + :g_strdup_printf(_("Register New Account at %s"), from); + purple_request_fields(js->gc, title, + title, instructions, fields, + registered?_("Change Registration"):_("Register"), G_CALLBACK(jabber_register_cb), + _("Cancel"), G_CALLBACK(jabber_register_cancel_cb), + purple_connection_get_account(js->gc), NULL, NULL, + cbdata); + g_free(title); + } g_free(instructions); } -} void jabber_register_start(JabberStream *js) { @@ -895,6 +1032,14 @@ jabber_iq_send(iq); } +void jabber_register_gateway(JabberStream *js, const char *gateway) { + JabberIq *iq; + + iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:register"); + xmlnode_set_attrib(iq->node, "to", gateway); + jabber_iq_send(iq); +} + void jabber_register_account(PurpleAccount *account) { PurpleConnection *gc = purple_account_get_connection(account); @@ -913,6 +1058,7 @@ g_free, g_free); js->user = jabber_id_new(purple_account_get_username(account)); js->next_id = g_random_int(); + js->old_length = -1; if(!js->user) { purple_connection_error(gc, _("Invalid XMPP ID")); @@ -966,6 +1112,65 @@ } } +static void jabber_unregister_account_iq_cb(JabberStream *js, xmlnode *packet, gpointer data) { + PurpleAccount *account = purple_connection_get_account(js->gc); + const char *type = xmlnode_get_attrib(packet,"type"); + if(!strcmp(type,"error")) { + char *msg = jabber_parse_error(js, packet); + + purple_notify_error(js->gc, _("Error unregistering account"), + _("Error unregistering account"), msg); + g_free(msg); + if(js->unregistration_cb) + js->unregistration_cb(account, FALSE, js->unregistration_user_data); + } else if(!strcmp(type,"result")) { + purple_notify_info(js->gc, _("Account successfully unregistered"), + _("Account successfully unregistered"), NULL); + if(js->unregistration_cb) + js->unregistration_cb(account, TRUE, js->unregistration_user_data); + } +} + +static void jabber_unregister_account_cb(JabberStream *js) { + JabberIq *iq; + xmlnode *query; + assert(js->unregistration); + + iq = jabber_iq_new_query(js,JABBER_IQ_SET,"jabber:iq:register"); + assert(iq); + query = xmlnode_get_child_with_namespace(iq->node,"query","jabber:iq:register"); + assert(query); + xmlnode_new_child(query,"remove"); + + xmlnode_set_attrib(iq->node,"to",js->user->domain); + jabber_iq_set_callback(iq,jabber_unregister_account_iq_cb,NULL); + + jabber_iq_send(iq); +} + +void jabber_unregister_account(PurpleAccount *account, PurpleAccountUnregistrationCb cb, void *user_data) { + PurpleConnection *gc = purple_account_get_connection(account); + JabberStream *js; + + if(gc->state != PURPLE_CONNECTED) { + if(gc->state != PURPLE_CONNECTING) + jabber_login(account); + js = gc->proto_data; + js->unregistration = TRUE; + js->unregistration_cb = cb; + js->unregistration_user_data = user_data; + return; + } + + js = gc->proto_data; + assert(!js->unregistration); /* don't allow multiple calls */ + js->unregistration = TRUE; + js->unregistration_cb = cb; + js->unregistration_user_data = user_data; + + jabber_unregister_account_cb(js); +} + void jabber_close(PurpleConnection *gc) { JabberStream *js = gc->proto_data; @@ -993,6 +1198,8 @@ jabber_buddy_remove_all_pending_buddy_info_requests(js); + jabber_parser_free(js); + if(js->iq_callbacks) g_hash_table_destroy(js->iq_callbacks); if(js->disco_callbacks) @@ -1025,12 +1232,35 @@ g_string_free(js->sasl_mechs, TRUE); if(js->sasl_cb) g_free(js->sasl_cb); +#endif if(js->serverFQDN) g_free(js->serverFQDN); -#endif + while(js->commands) { + JabberAdHocCommands *cmd = js->commands->data; + g_free(cmd->jid); + g_free(cmd->node); + g_free(cmd->name); + g_free(cmd); + js->commands = g_list_delete_link(js->commands, js->commands); + } g_free(js->server_name); g_free(js->gmail_last_time); g_free(js->gmail_last_tid); + if(js->old_msg) + g_free(js->old_msg); + if(js->old_avatarhash) + g_free(js->old_avatarhash); + if(js->old_artist) + g_free(js->old_artist); + if(js->old_title) + g_free(js->old_title); + if(js->old_source) + g_free(js->old_source); + if(js->old_uri) + g_free(js->old_uri); + if(js->old_track) + g_free(js->old_track); + g_free(js); gc->proto_data = NULL; @@ -1051,9 +1281,13 @@ js->gsc ? 5 : 2, JABBER_CONNECT_STEPS); jabber_stream_init(js); break; + case JABBER_STREAM_INITIALIZING_ENCRYPTION: + purple_connection_update_progress(js->gc, _("Initializing SSL/TLS"), + 6, JABBER_CONNECT_STEPS); + break; case JABBER_STREAM_AUTHENTICATING: purple_connection_update_progress(js->gc, _("Authenticating"), - js->gsc ? 6 : 3, JABBER_CONNECT_STEPS); + js->gsc ? 7 : 3, JABBER_CONNECT_STEPS); if(js->protocol_version == JABBER_PROTO_0_9 && js->registration) { jabber_register_start(js); } else if(js->auth_type == JABBER_AUTH_IQ_AUTH) { @@ -1062,7 +1296,7 @@ break; case JABBER_STREAM_REINITIALIZING: purple_connection_update_progress(js->gc, _("Re-initializing Stream"), - (js->gsc ? 7 : 4), JABBER_CONNECT_STEPS); + (js->gsc ? 8 : 4), JABBER_CONNECT_STEPS); /* The stream will be reinitialized later, in jabber_recv_cb_ssl() */ js->reinit = TRUE; @@ -1089,6 +1323,38 @@ js->idle = idle ? time(NULL) - idle : idle; } +void jabber_add_feature(const char *shortname, const char *namespace, JabberFeatureEnabled cb) { + JabberFeature *feat; + + assert(shortname != NULL); + assert(namespace != NULL); + + feat = g_new0(JabberFeature,1); + feat->shortname = g_strdup(shortname); + feat->namespace = g_strdup(namespace); + feat->is_enabled = cb; + + /* try to remove just in case it already exists in the list */ + jabber_remove_feature(shortname); + + jabber_features = g_list_append(jabber_features, feat); +} + +void jabber_remove_feature(const char *shortname) { + GList *feature; + for(feature = jabber_features; feature; feature = feature->next) { + JabberFeature *feat = (JabberFeature*)feature->data; + if(!strcmp(feat->shortname, shortname)) { + g_free(feat->shortname); + g_free(feat->namespace); + + g_free(feature->data); + feature = g_list_delete_link(feature, feature); + break; + } + } +} + const char *jabber_list_icon(PurpleAccount *a, PurpleBuddy *b) { return "jabber"; @@ -1161,8 +1427,12 @@ JabberBuddyResource *jbr = NULL; const char *sub; GList *l; + const char *mood; if (full) { + PurpleStatus *status; + PurpleValue *value; + if(jb->subscription & JABBER_SUB_FROM) { if(jb->subscription & JABBER_SUB_TO) sub = _("Both"); @@ -1180,6 +1450,20 @@ } purple_notify_user_info_add_pair(user_info, _("Subscription"), sub); + + status = purple_presence_get_active_status(purple_buddy_get_presence(b)); + value = purple_status_get_attr_value(status, "mood"); + if (value && purple_value_get_type(value) == PURPLE_TYPE_STRING && (mood = purple_value_get_string(value))) { + + value = purple_status_get_attr_value(status, "moodtext"); + if(value && purple_value_get_type(value) == PURPLE_TYPE_STRING) { + char *moodplustext = g_strdup_printf("%s (%s)",mood,purple_value_get_string(value)); + + purple_notify_user_info_add_pair(user_info, _("Mood"), moodplustext); + g_free(moodplustext); + } else + purple_notify_user_info_add_pair(user_info, _("Mood"), mood); + } } for(l=jb->resources; l; l = l->next) { @@ -1242,6 +1526,19 @@ NULL, TRUE, TRUE, FALSE, "priority", _("Priority"), priority_value, "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING), + "mood", _("Mood"), purple_value_new(PURPLE_TYPE_STRING), + "moodtext", _("Mood Text"), purple_value_new(PURPLE_TYPE_STRING), + "tune_artist", _("Tune Artist"), purple_value_new(PURPLE_TYPE_STRING), + "tune_title", _("Tune Title"), purple_value_new(PURPLE_TYPE_STRING), + "tune_album", _("Tune Album"), purple_value_new(PURPLE_TYPE_STRING), + "tune_genre", _("Tune Genre"), purple_value_new(PURPLE_TYPE_STRING), + "tune_comment", _("Tune Comment"), purple_value_new(PURPLE_TYPE_STRING), + "tune_track", _("Tune Track"), purple_value_new(PURPLE_TYPE_STRING), + "tune_time", _("Tune Time"), purple_value_new(PURPLE_TYPE_INT), + "tune_year", _("Tune Year"), purple_value_new(PURPLE_TYPE_INT), + "tune_url", _("Tune URL"), purple_value_new(PURPLE_TYPE_STRING), + "nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING), + "buzz", _("Allow Buzz"), purple_value_new(PURPLE_TYPE_BOOLEAN), NULL); types = g_list_append(types, type); @@ -1252,6 +1549,19 @@ _("Chatty"), TRUE, TRUE, FALSE, "priority", _("Priority"), priority_value, "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING), + "mood", _("Mood"), purple_value_new(PURPLE_TYPE_STRING), + "moodtext", _("Mood Text"), purple_value_new(PURPLE_TYPE_STRING), + "tune_artist", _("Tune Artist"), purple_value_new(PURPLE_TYPE_STRING), + "tune_title", _("Tune Title"), purple_value_new(PURPLE_TYPE_STRING), + "tune_album", _("Tune Album"), purple_value_new(PURPLE_TYPE_STRING), + "tune_genre", _("Tune Genre"), purple_value_new(PURPLE_TYPE_STRING), + "tune_comment", _("Tune Comment"), purple_value_new(PURPLE_TYPE_STRING), + "tune_track", _("Tune Track"), purple_value_new(PURPLE_TYPE_STRING), + "tune_time", _("Tune Time"), purple_value_new(PURPLE_TYPE_INT), + "tune_year", _("Tune Year"), purple_value_new(PURPLE_TYPE_INT), + "tune_url", _("Tune URL"), purple_value_new(PURPLE_TYPE_STRING), + "nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING), + "buzz", _("Allow Buzz"), purple_value_new(PURPLE_TYPE_BOOLEAN), NULL); types = g_list_append(types, type); @@ -1262,6 +1572,19 @@ NULL, TRUE, TRUE, FALSE, "priority", _("Priority"), priority_value, "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING), + "mood", _("Mood"), purple_value_new(PURPLE_TYPE_STRING), + "moodtext", _("Mood Text"), purple_value_new(PURPLE_TYPE_STRING), + "tune_artist", _("Tune Artist"), purple_value_new(PURPLE_TYPE_STRING), + "tune_title", _("Tune Title"), purple_value_new(PURPLE_TYPE_STRING), + "tune_album", _("Tune Album"), purple_value_new(PURPLE_TYPE_STRING), + "tune_genre", _("Tune Genre"), purple_value_new(PURPLE_TYPE_STRING), + "tune_comment", _("Tune Comment"), purple_value_new(PURPLE_TYPE_STRING), + "tune_track", _("Tune Track"), purple_value_new(PURPLE_TYPE_STRING), + "tune_time", _("Tune Time"), purple_value_new(PURPLE_TYPE_INT), + "tune_year", _("Tune Year"), purple_value_new(PURPLE_TYPE_INT), + "tune_url", _("Tune URL"), purple_value_new(PURPLE_TYPE_STRING), + "nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING), + "buzz", _("Allow Buzz"), purple_value_new(PURPLE_TYPE_BOOLEAN), NULL); types = g_list_append(types, type); @@ -1272,6 +1595,19 @@ NULL, TRUE, TRUE, FALSE, "priority", _("Priority"), priority_value, "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING), + "mood", _("Mood"), purple_value_new(PURPLE_TYPE_STRING), + "moodtext", _("Mood Text"), purple_value_new(PURPLE_TYPE_STRING), + "tune_artist", _("Tune Artist"), purple_value_new(PURPLE_TYPE_STRING), + "tune_title", _("Tune Title"), purple_value_new(PURPLE_TYPE_STRING), + "tune_album", _("Tune Album"), purple_value_new(PURPLE_TYPE_STRING), + "tune_genre", _("Tune Genre"), purple_value_new(PURPLE_TYPE_STRING), + "tune_comment", _("Tune Comment"), purple_value_new(PURPLE_TYPE_STRING), + "tune_track", _("Tune Track"), purple_value_new(PURPLE_TYPE_STRING), + "tune_time", _("Tune Time"), purple_value_new(PURPLE_TYPE_INT), + "tune_year", _("Tune Year"), purple_value_new(PURPLE_TYPE_INT), + "tune_url", _("Tune URL"), purple_value_new(PURPLE_TYPE_STRING), + "nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING), + "buzz", _("Allow Buzz"), purple_value_new(PURPLE_TYPE_BOOLEAN), NULL); types = g_list_append(types, type); @@ -1282,6 +1618,19 @@ _("Do Not Disturb"), TRUE, TRUE, FALSE, "priority", _("Priority"), priority_value, "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING), + "mood", _("Mood"), purple_value_new(PURPLE_TYPE_STRING), + "moodtext", _("Mood Text"), purple_value_new(PURPLE_TYPE_STRING), + "tune_artist", _("Tune Artist"), purple_value_new(PURPLE_TYPE_STRING), + "tune_title", _("Tune Title"), purple_value_new(PURPLE_TYPE_STRING), + "tune_album", _("Tune Album"), purple_value_new(PURPLE_TYPE_STRING), + "tune_genre", _("Tune Genre"), purple_value_new(PURPLE_TYPE_STRING), + "tune_comment", _("Tune Comment"), purple_value_new(PURPLE_TYPE_STRING), + "tune_track", _("Tune Track"), purple_value_new(PURPLE_TYPE_STRING), + "tune_time", _("Tune Time"), purple_value_new(PURPLE_TYPE_INT), + "tune_year", _("Tune Year"), purple_value_new(PURPLE_TYPE_INT), + "tune_url", _("Tune URL"), purple_value_new(PURPLE_TYPE_STRING), + "nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING), + "buzz", _("Allow Buzz"), purple_value_new(PURPLE_TYPE_BOOLEAN), NULL); types = g_list_append(types, type); @@ -1388,6 +1737,8 @@ GList *jabber_actions(PurplePlugin *plugin, gpointer context) { + PurpleConnection *gc = (PurpleConnection *) context; + JabberStream *js = gc->proto_data; GList *m = NULL; PurplePluginAction *act; @@ -1405,6 +1756,14 @@ jabber_user_search_begin); m = g_list_append(m, act); + purple_debug_info("jabber", "jabber_actions: have pep: %s\n", js->pep?"YES":"NO"); + + if(js->pep) + jabber_pep_init_actions(&m); + + if(js->commands) + jabber_adhoc_init_server_commands(js, &m); + return m; } @@ -1809,6 +2168,71 @@ return PURPLE_CMD_RET_OK; } +static PurpleCmdRet jabber_cmd_ping(PurpleConversation *conv, + const char *cmd, char **args, char **error, void *data) +{ + if(!args || !args[0]) + return PURPLE_CMD_RET_FAILED; + + if(!jabber_ping_jid(conv, args[0])) { + *error = g_strdup_printf(_("Unable to ping user %s"), args[0]); + return PURPLE_CMD_RET_FAILED; + } + + return PURPLE_CMD_RET_OK; +} + +static PurpleCmdRet jabber_cmd_buzz(PurpleConversation *conv, + const char *cmd, char **args, char **error, void *data) +{ + JabberStream *js = conv->account->gc->proto_data; + xmlnode *msg, *buzz; + JabberBuddy *jb; + JabberBuddyResource *jbr; + char *to; + GList *iter; + + if(!args || !args[0]) + return PURPLE_CMD_RET_FAILED; + + jb = jabber_buddy_find(js, args[0], FALSE); + if(!jb) { + *error = g_strdup_printf(_("Unable to buzz, because there is nothing known about user %s."), args[0]); + return PURPLE_CMD_RET_FAILED; + } + + jbr = jabber_buddy_find_resource(jb, NULL); + if(!jbr) { + *error = g_strdup_printf(_("Unable to buzz, because user %s might be offline."), args[0]); + return PURPLE_CMD_RET_FAILED; + } + if(!jbr->caps) { + *error = g_strdup_printf(_("Unable to buzz, because there is nothing known about user %s."), args[0]); + return PURPLE_CMD_RET_FAILED; + } + for(iter = jbr->caps->features; iter; iter = g_list_next(iter)) { + if(!strcmp(iter->data, "http://www.xmpp.org/extensions/xep-0224.html#ns")) { + msg = xmlnode_new("message"); + to = g_strdup_printf("%s/%s", args[0], jbr->name); + xmlnode_set_attrib(msg,"to",to); + g_free(to); + + /* avoid offline storage */ + xmlnode_set_attrib(msg,"type","headline"); + + buzz = xmlnode_new_child(msg,"attention"); + xmlnode_set_namespace(buzz,"http://www.xmpp.org/extensions/xep-0224.html#ns"); + + jabber_send(js,msg); + xmlnode_free(msg); + + return PURPLE_CMD_RET_OK; + } + } + *error = g_strdup_printf(_("Unable to buzz, because the user %s does not support it."), args[0]); + return PURPLE_CMD_RET_FAILED; +} + gboolean jabber_offline_message(const PurpleBuddy *buddy) { return TRUE; @@ -1886,6 +2310,16 @@ "prpl-jabber", jabber_cmd_chat_msg, _("msg <user> <message>: Send a private message to another user."), NULL); + purple_cmd_register("ping", "w", PURPLE_CMD_P_PRPL, + PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_IM | + PURPLE_CMD_FLAG_PRPL_ONLY, + "prpl-jabber", jabber_cmd_ping, + _("ping <jid>: Ping a user/component/server."), + NULL); + purple_cmd_register("buzz", "s", PURPLE_CMD_P_PRPL, + PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_PRPL_ONLY, + "prpl-jabber", jabber_cmd_buzz, + _("buzz: Buzz a user to get their attention"), NULL); } void
--- a/libpurple/protocols/jabber/jabber.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/jabber/jabber.h Wed Sep 12 19:11:38 2007 +0000 @@ -17,28 +17,11 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _PURPLE_JABBER_H_ #define _PURPLE_JABBER_H_ -#include <libxml/parser.h> -#include <glib.h> -#include "circbuffer.h" -#include "connection.h" -#include "dnssrv.h" -#include "roomlist.h" -#include "sslconn.h" - -#include "jutil.h" -#include "xmlnode.h" - -#ifdef HAVE_CYRUS_SASL -#include <sasl/sasl.h> -#endif - -#define CAPS0115_NODE "http://pidgin.im/caps" - typedef enum { JABBER_CAP_NONE = 0, JABBER_CAP_XHTML = 1 << 0, @@ -51,25 +34,49 @@ JABBER_CAP_IQ_SEARCH = 1 << 7, JABBER_CAP_IQ_REGISTER = 1 << 8, - /* Google Talk extensions: + /* Google Talk extensions: * http://code.google.com/apis/talk/jep_extensions/extensions.html */ JABBER_CAP_GMAIL_NOTIFY = 1 << 9, JABBER_CAP_GOOGLE_ROSTER = 1 << 10, + JABBER_CAP_PING = 1 << 11, + JABBER_CAP_ADHOC = 1 << 12, + JABBER_CAP_RETRIEVED = 1 << 31 } JabberCapabilities; +typedef struct _JabberStream JabberStream; + +#include <libxml/parser.h> +#include <glib.h> +#include "circbuffer.h" +#include "connection.h" +#include "dnssrv.h" +#include "roomlist.h" +#include "sslconn.h" + +#include "jutil.h" +#include "xmlnode.h" +#include "buddy.h" + +#ifdef HAVE_CYRUS_SASL +#include <sasl/sasl.h> +#endif + +#define CAPS0115_NODE "http://pidgin.im/caps" + typedef enum { JABBER_STREAM_OFFLINE, JABBER_STREAM_CONNECTING, JABBER_STREAM_INITIALIZING, + JABBER_STREAM_INITIALIZING_ENCRYPTION, JABBER_STREAM_AUTHENTICATING, JABBER_STREAM_REINITIALIZING, JABBER_STREAM_CONNECTED } JabberStreamState; -typedef struct _JabberStream +struct _JabberStream { int fd; @@ -136,6 +143,8 @@ char *gmail_last_time; char *gmail_last_tid; + 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 */ @@ -150,13 +159,50 @@ int sasl_state; int sasl_maxbuf; GString *sasl_mechs; - char *serverFQDN; + gboolean unregistration; + PurpleAccountUnregistrationCb unregistration_cb; + void *unregistration_user_data; + gboolean vcard_fetched; -} JabberStream; + /* does the local server support PEP? */ + gboolean pep; -void jabber_process_packet(JabberStream *js, xmlnode *packet); + /* Is Buzz enabled? */ + gboolean allowBuzz; + + /* A list of JabberAdHocCommands supported by the server */ + GList *commands; + + /* last presence update to check for differences */ + JabberBuddyState old_state; + char *old_msg; + int old_priority; + char *old_avatarhash; + + /* same for user tune */ + char *old_artist; + char *old_title; + char *old_source; + char *old_uri; + int old_length; + char *old_track; +}; + +typedef gboolean (JabberFeatureEnabled)(JabberStream *js, const gchar *shortname, const gchar *namespace); + +typedef struct _JabberFeature +{ + gchar *shortname; + gchar *namespace; + JabberFeatureEnabled *is_enabled; +} JabberFeature; + +/* what kind of additional features as returned from disco#info are supported? */ +extern GList *jabber_features; + +void jabber_process_packet(JabberStream *js, xmlnode **packet); void jabber_send(JabberStream *js, xmlnode *data); void jabber_send_raw(JabberStream *js, const char *data, int len); @@ -169,6 +215,9 @@ char *jabber_parse_error(JabberStream *js, xmlnode *packet); +void jabber_add_feature(const gchar *shortname, const gchar *namespace, JabberFeatureEnabled cb); /* cb may be NULL */ +void jabber_remove_feature(const gchar *shortname); + /** PRPL functions */ const char *jabber_list_icon(PurpleAccount *a, PurpleBuddy *b); const char* jabber_list_emblem(PurpleBuddy *b); @@ -179,7 +228,9 @@ void jabber_close(PurpleConnection *gc); void jabber_idle_set(PurpleConnection *gc, int idle); void jabber_keepalive(PurpleConnection *gc); +void jabber_register_gateway(JabberStream *js, const char *gateway); void jabber_register_account(PurpleAccount *account); +void jabber_unregister_account(PurpleAccount *account, PurpleAccountUnregistrationCb cb, void *user_data); void jabber_convo_closed(PurpleConnection *gc, const char *who); PurpleChat *jabber_find_blist_chat(PurpleAccount *account, const char *name); gboolean jabber_offline_message(const PurpleBuddy *buddy);
--- a/libpurple/protocols/jabber/jutil.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/jabber/jutil.c Wed Sep 12 19:11:38 2007 +0000 @@ -15,7 +15,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */ #include "internal.h"
--- a/libpurple/protocols/jabber/jutil.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/jabber/jutil.h Wed Sep 12 19:11:38 2007 +0000 @@ -17,11 +17,13 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _PURPLE_JABBER_JUTIL_H_ #define _PURPLE_JABBER_JUTIL_H_ +#include "account.h" + typedef struct _JabberID { char *node; char *domain;
--- a/libpurple/protocols/jabber/libxmpp.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/jabber/libxmpp.c Wed Sep 12 19:11:38 2007 +0000 @@ -16,7 +16,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */ @@ -39,6 +39,9 @@ #include "message.h" #include "presence.h" #include "google.h" +#include "pep.h" +#include "usertune.h" +#include "caps.h" static PurplePluginProtocolInfo prpl_info = { @@ -52,7 +55,7 @@ #endif NULL, /* user_splits */ NULL, /* protocol_options */ - {"png,gif,jpeg", 32, 32, 96, 96, 8191, PURPLE_ICON_SCALE_SEND | PURPLE_ICON_SCALE_DISPLAY}, /* icon_spec */ + {"png", 32, 32, 96, 96, 8191, PURPLE_ICON_SCALE_SEND | PURPLE_ICON_SCALE_DISPLAY}, /* icon_spec */ jabber_list_icon, /* list_icon */ jabber_list_emblem, /* list_emblems */ jabber_status_text, /* status_text */ @@ -111,11 +114,11 @@ NULL, /* whiteboard_prpl_ops */ jabber_prpl_send_raw, /* send_raw */ jabber_roomlist_room_serialize, /* roomlist_room_serialize */ + jabber_unregister_account, /* unregister_user */ /* padding */ NULL, NULL, - NULL, NULL }; @@ -191,49 +194,63 @@ static void init_plugin(PurplePlugin *plugin) { - PurpleAccountUserSplit *split; - PurpleAccountOption *option; - + PurpleAccountUserSplit *split; + PurpleAccountOption *option; + /* Translators: 'domain' is used here in the context of Internet domains, e.g. pidgin.im */ - split = purple_account_user_split_new(_("Domain"), NULL, '@'); - purple_account_user_split_set_reverse(split, FALSE); - prpl_info.user_splits = g_list_append(prpl_info.user_splits, split); - - split = purple_account_user_split_new(_("Resource"), "Home", '/'); - 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(_("Force old (port 5223) SSL"), "old_ssl", FALSE); - prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, - option); - - option = purple_account_option_bool_new( - _("Allow plaintext auth over unencrypted streams"), - "auth_plain_in_clear", FALSE); - prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, - option); + split = purple_account_user_split_new(_("Domain"), NULL, '@'); + purple_account_user_split_set_reverse(split, FALSE); + prpl_info.user_splits = g_list_append(prpl_info.user_splits, split); + + split = purple_account_user_split_new(_("Resource"), "Home", '/'); + 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", FALSE); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, + option); + + option = purple_account_option_bool_new(_("Force old (port 5223) SSL"), "old_ssl", FALSE); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, + option); + + option = purple_account_option_bool_new( + _("Allow plaintext auth over unencrypted streams"), + "auth_plain_in_clear", FALSE); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, + option); + + option = purple_account_option_int_new(_("Connect port"), "port", 5222); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, + option); + + option = purple_account_option_string_new(_("Connect server"), + "connect_server", NULL); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, + option); + + + jabber_init_plugin(plugin); + + purple_prefs_remove("/plugins/prpl/jabber"); + + /* XXX - If any other plugin wants SASL this won't be good ... */ +#ifdef HAVE_CYRUS_SASL + sasl_client_init(NULL); +#endif + jabber_register_commands(); + + jabber_iq_init(); + jabber_pep_init(); + + jabber_tune_init(); + jabber_caps_init(); - option = purple_account_option_int_new(_("Connect port"), "port", 5222); - prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, - option); - - option = purple_account_option_string_new(_("Connect server"), - "connect_server", NULL); - prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, - option); - - - jabber_init_plugin(plugin); - - purple_prefs_remove("/plugins/prpl/jabber"); - - /* XXX - If any other plugin wants SASL this won't be good ... */ -#ifdef HAVE_CYRUS_SASL - sasl_client_init(NULL); -#endif - jabber_register_commands(); - - jabber_iq_init(); + jabber_add_feature("avatarmeta", AVATARNAMESPACEMETA, jabber_pep_namespace_only_when_pep_enabled_cb); + jabber_add_feature("avatardata", AVATARNAMESPACEDATA, jabber_pep_namespace_only_when_pep_enabled_cb); + jabber_add_feature("buzz", "http://www.xmpp.org/extensions/xep-0224.html#ns", jabber_buzz_isenabled); + + jabber_pep_register_handler("avatar", AVATARNAMESPACEMETA, jabber_buddy_avatar_update_metadata); }
--- a/libpurple/protocols/jabber/message.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/jabber/message.c Wed Sep 12 19:11:38 2007 +0000 @@ -15,7 +15,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */ #include "internal.h" @@ -30,6 +30,7 @@ #include "google.h" #include "message.h" #include "xmlnode.h" +#include "pep.h" void jabber_message_free(JabberMessage *jm) { @@ -147,9 +148,13 @@ static void handle_headline(JabberMessage *jm) { char *title; - GString *body = g_string_new(""); + GString *body; GList *etc; + if(!jm->xhtml && !jm->body) + return; /* ignore headlines without any content */ + + body = g_string_new(""); title = g_strdup_printf(_("Message from %s"), jm->from); if(jm->xhtml) @@ -273,6 +278,36 @@ g_free(buf); } +static void handle_buzz(JabberMessage *jm) { + PurpleBuddy *buddy; + PurpleAccount *account; + PurpleConversation *c; + char *username, *str; + + /* Delayed buzz MUST NOT be accepted */ + if(jm->delayed) + return; + + /* Reject buzz when it's not enabled */ + if(!jm->js->allowBuzz) + return; + + account = purple_connection_get_account(jm->js->gc); + + if ((buddy = purple_find_buddy(account, jm->from)) != NULL) + username = g_markup_escape_text(purple_buddy_get_alias(buddy), -1); + else + return; /* Do not accept buzzes from unknown people */ + + c = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, jm->from); + + str = g_strdup_printf(_("%s has buzzed you!"), username); + + purple_conversation_write(c, NULL, str, PURPLE_MESSAGE_SYSTEM|PURPLE_MESSAGE_NOTIFY, time(NULL)); + g_free(username); + g_free(str); +} + void jabber_message_parse(JabberStream *js, xmlnode *packet) { JabberMessage *jm; @@ -308,22 +343,25 @@ jm->id = g_strdup(xmlnode_get_attrib(packet, "id")); for(child = packet->child; child; child = child->next) { + const char *xmlns = xmlnode_get_namespace(child); + if(!xmlns) + xmlns = ""; if(child->type != XMLNODE_TYPE_TAG) continue; - if(!strcmp(child->name, "subject")) { + if(!strcmp(child->name, "subject") && !strcmp(xmlns,"jabber:client")) { if(!jm->subject) jm->subject = xmlnode_get_data(child); - } else if(!strcmp(child->name, "thread")) { + } else if(!strcmp(child->name, "thread") && !strcmp(xmlns,"jabber:client")) { if(!jm->thread_id) jm->thread_id = xmlnode_get_data(child); - } else if(!strcmp(child->name, "body")) { + } else if(!strcmp(child->name, "body") && !strcmp(xmlns,"jabber:client")) { if(!jm->body) { char *msg = xmlnode_to_str(child, NULL); jm->body = purple_strdup_withhtml(msg); g_free(msg); } - } else if(!strcmp(child->name, "html")) { + } else if(!strcmp(child->name, "html") && !strcmp(xmlns,"http://jabber.org/protocol/xhtml-im")) { if(!jm->xhtml && xmlnode_get_child(child, "body")) { char *c; jm->xhtml = xmlnode_to_str(child, NULL); @@ -335,21 +373,28 @@ *c = ' '; } } - } else if(!strcmp(child->name, "active")) { + } else if(!strcmp(child->name, "active") && !strcmp(xmlns,"http://jabber.org/protocol/chatstates")) { jm->chat_state = JM_STATE_ACTIVE; jm->typing_style |= JM_TS_JEP_0085; - } else if(!strcmp(child->name, "composing")) { + } else if(!strcmp(child->name, "composing") && !strcmp(xmlns,"http://jabber.org/protocol/chatstates")) { jm->chat_state = JM_STATE_COMPOSING; jm->typing_style |= JM_TS_JEP_0085; - } else if(!strcmp(child->name, "paused")) { + } else if(!strcmp(child->name, "paused") && !strcmp(xmlns,"http://jabber.org/protocol/chatstates")) { jm->chat_state = JM_STATE_PAUSED; jm->typing_style |= JM_TS_JEP_0085; - } else if(!strcmp(child->name, "inactive")) { + } else if(!strcmp(child->name, "inactive") && !strcmp(xmlns,"http://jabber.org/protocol/chatstates")) { jm->chat_state = JM_STATE_INACTIVE; jm->typing_style |= JM_TS_JEP_0085; - } else if(!strcmp(child->name, "gone")) { + } else if(!strcmp(child->name, "gone") && !strcmp(xmlns,"http://jabber.org/protocol/chatstates")) { jm->chat_state = JM_STATE_GONE; jm->typing_style |= JM_TS_JEP_0085; + } else if(!strcmp(child->name, "event") && !strcmp(xmlns,"http://jabber.org/protocol/pubsub#event")) { + xmlnode *items; + jm->type = JABBER_MESSAGE_EVENT; + for(items = xmlnode_get_child(child,"items"); items; items = items->next) + jm->eventitems = g_list_append(jm->eventitems, items); + } else if(!strcmp(child->name, "attention") && !strcmp(xmlns,"http://www.xmpp.org/extensions/xep-0224.html#ns")) { + jm->hasBuzz = TRUE; } else if(!strcmp(child->name, "error")) { const char *code = xmlnode_get_attrib(child, "code"); char *code_txt = NULL; @@ -364,8 +409,12 @@ g_free(code_txt); g_free(text); + } else if(!strcmp(child->name, "delay") && xmlns && !strcmp(xmlns,"urn:xmpp:delay")) { + const char *timestamp = xmlnode_get_attrib(child, "stamp"); + jm->delayed = TRUE; + if(timestamp) + jm->sent = purple_str_to_time(timestamp, TRUE, NULL, NULL, NULL); } else if(!strcmp(child->name, "x")) { - const char *xmlns = xmlnode_get_namespace(child); if(xmlns && !strcmp(xmlns, "jabber:x:event")) { if(xmlnode_get_child(child, "composing")) { if(jm->chat_state == JM_STATE_ACTIVE) @@ -411,6 +460,9 @@ } } + if(jm->hasBuzz) + handle_buzz(jm); + switch(jm->type) { case JABBER_MESSAGE_NORMAL: case JABBER_MESSAGE_CHAT: @@ -425,6 +477,9 @@ case JABBER_MESSAGE_GROUPCHAT_INVITE: handle_groupchat_invite(jm); break; + case JABBER_MESSAGE_EVENT: + jabber_handle_event(jm); + break; case JABBER_MESSAGE_ERROR: handle_error(jm); break; @@ -461,6 +516,7 @@ type = "error"; break; case JABBER_MESSAGE_OTHER: + default: type = NULL; break; } @@ -689,3 +745,8 @@ jabber_message_send(jm); jabber_message_free(jm); } + +gboolean jabber_buzz_isenabled(JabberStream *js, const gchar *shortname, const gchar *namespace) { + return js->allowBuzz; +} +
--- a/libpurple/protocols/jabber/message.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/jabber/message.h Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _PURPLE_JABBER_MESSAGE_H_ #define _PURPLE_JABBER_MESSAGE_H_ @@ -35,10 +35,12 @@ JABBER_MESSAGE_HEADLINE, JABBER_MESSAGE_ERROR, JABBER_MESSAGE_GROUPCHAT_INVITE, + JABBER_MESSAGE_EVENT, JABBER_MESSAGE_OTHER } type; time_t sent; gboolean delayed; + gboolean hasBuzz; char *id; char *from; char *to; @@ -61,6 +63,7 @@ JM_STATE_GONE } chat_state; GList *etc; + GList *eventitems; } JabberMessage; void jabber_message_free(JabberMessage *jm); @@ -75,4 +78,6 @@ unsigned int jabber_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state); void jabber_message_conv_closed(JabberStream *js, const char *who); +gboolean jabber_buzz_isenabled(JabberStream *js, const gchar *shortname, const gchar *namespace); + #endif /* _PURPLE_JABBER_MESSAGE_H_ */
--- a/libpurple/protocols/jabber/oob.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/jabber/oob.c Wed Sep 12 19:11:38 2007 +0000 @@ -15,7 +15,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */ #include "internal.h"
--- a/libpurple/protocols/jabber/oob.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/jabber/oob.h Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _PURPLE_JABBER_OOB_H_ #define _PURPLE_JABBER_OOB_H_
--- a/libpurple/protocols/jabber/parser.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/jabber/parser.c Wed Sep 12 19:11:38 2007 +0000 @@ -15,7 +15,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */ #include "internal.h" @@ -63,7 +63,7 @@ if(js->protocol_version == JABBER_PROTO_0_9) js->auth_type = JABBER_AUTH_IQ_AUTH; - if(js->state == JABBER_STREAM_INITIALIZING) + if(js->state == JABBER_STREAM_INITIALIZING || js->state == JABBER_STREAM_INITIALIZING_ENCRYPTION) jabber_stream_set_state(js, JABBER_STREAM_AUTHENTICATING); } else { @@ -113,7 +113,7 @@ } else { xmlnode *packet = js->current; js->current = NULL; - jabber_process_packet(js, packet); + jabber_process_packet(js, &packet); xmlnode_free(packet); } } @@ -174,6 +174,10 @@ * the parser context when you try to use it (this way, it can figure * out the encoding at creation time. So, setting up the parser is * just a matter of destroying any current parser. */ + jabber_parser_free(js); +} + +void jabber_parser_free(JabberStream *js) { if (js->context) { xmlParseChunk(js->context, NULL,0,1); xmlFreeParserCtxt(js->context); @@ -181,7 +185,6 @@ } } - void jabber_parser_process(JabberStream *js, const char *buf, int len) { if (js->context == NULL) {
--- a/libpurple/protocols/jabber/parser.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/jabber/parser.h Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _PURPLE_JABBER_PARSER_H_ #define _PURPLE_JABBER_PARSER_H_ @@ -25,6 +25,7 @@ #include "jabber.h" void jabber_parser_setup(JabberStream *js); +void jabber_parser_free(JabberStream *js); void jabber_parser_process(JabberStream *js, const char *buf, int len); #endif /* _PURPLE_JABBER_PARSER_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/pep.c Wed Sep 12 19:11:38 2007 +0000 @@ -0,0 +1,130 @@ +/* + * purple - Jabber Protocol Plugin + * + * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "internal.h" + +#include "pep.h" +#include "iq.h" +#include <string.h> +#include "usermood.h" +#include "usernick.h" + +static GHashTable *pep_handlers = NULL; + +void jabber_pep_init(void) { + if(!pep_handlers) { + pep_handlers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + + /* register PEP handlers */ + jabber_mood_init(); + jabber_nick_init(); + } +} + +void jabber_pep_init_actions(GList **m) { + /* register the PEP-specific actions */ + jabber_mood_init_action(m); + jabber_nick_init_action(m); +} + +void jabber_pep_register_handler(const char *shortname, const char *xmlns, JabberPEPHandler handlerfunc) { + gchar *notifyns = g_strdup_printf("%s+notify", xmlns); + jabber_add_feature(shortname, notifyns, NULL); /* receiving PEPs is always supported */ + g_free(notifyns); + g_hash_table_replace(pep_handlers, g_strdup(xmlns), handlerfunc); +} + +static void do_pep_iq_request_item_callback(JabberStream *js, xmlnode *packet, gpointer data) { + const char *from = xmlnode_get_attrib(packet,"from"); + xmlnode *pubsub = xmlnode_get_child_with_namespace(packet,"pubsub","http://jabber.org/protocol/pubsub#event"); + xmlnode *items = NULL; + JabberPEPHandler *cb = data; + + if(pubsub) + items = xmlnode_get_child(pubsub, "items"); + + cb(js, from, items); +} + +void jabber_pep_request_item(JabberStream *js, const char *to, const char *node, const char *id, JabberPEPHandler cb) { + JabberIq *iq = jabber_iq_new(js, JABBER_IQ_GET); + xmlnode *pubsub, *items, *item; + + xmlnode_set_attrib(iq->node,"to",to); + pubsub = xmlnode_new_child(iq->node,"pubsub"); + + xmlnode_set_namespace(pubsub,"http://jabber.org/protocol/pubsub"); + + items = xmlnode_new_child(pubsub, "items"); + xmlnode_set_attrib(items,"node",node); + + item = xmlnode_new_child(items, "item"); + if(id) + xmlnode_set_attrib(item, "id", id); + + jabber_iq_set_callback(iq,do_pep_iq_request_item_callback,(gpointer)cb); + + jabber_iq_send(iq); +} + +gboolean jabber_pep_namespace_only_when_pep_enabled_cb(JabberStream *js, const gchar *shortname, const gchar *namespace) { + return js->pep; +} + +void jabber_handle_event(JabberMessage *jm) { + /* this may be called even when the own server doesn't support pep! */ + JabberPEPHandler *jph; + GList *itemslist; + char *jid = jabber_get_bare_jid(jm->from); + + for(itemslist = jm->eventitems; itemslist; itemslist = itemslist->next) { + xmlnode *items = (xmlnode*)itemslist->data; + const char *nodename = xmlnode_get_attrib(items,"node"); + + if(nodename && (jph = g_hash_table_lookup(pep_handlers, nodename))) + jph(jm->js, jid, items); + } + + /* discard items we don't have a handler for */ + g_free(jid); +} + +void jabber_pep_publish(JabberStream *js, xmlnode *publish) { + JabberIq *iq; + xmlnode *pubsub; + + if(js->pep != TRUE) { + /* ignore when there's no PEP support on the server */ + xmlnode_free(publish); + return; + } + + iq = jabber_iq_new(js, JABBER_IQ_SET); + + pubsub = xmlnode_new("pubsub"); + xmlnode_set_namespace(pubsub, "http://jabber.org/protocol/pubsub"); + + xmlnode_insert_child(pubsub, publish); + + xmlnode_insert_child(iq->node, pubsub); + + jabber_iq_send(iq); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/pep.h Wed Sep 12 19:11:38 2007 +0000 @@ -0,0 +1,85 @@ +/* + * purple - Jabber Protocol Plugin + * + * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _PURPLE_JABBER_PEP_H_ +#define _PURPLE_JABBER_PEP_H_ + +#include "jabber.h" +#include "message.h" +#include "buddy.h" + +void jabber_pep_init(void); + +void jabber_pep_init_actions(GList **m); + +/* + * Callback for receiving PEP events. + * + * @parameter js The JabberStream this item was received on + * @parameter items The <items/>-tag with the <item/>-children + */ +typedef void (JabberPEPHandler)(JabberStream *js, const char *from, xmlnode *items); + +/* + * Registers a callback for PEP events. Also automatically announces this receiving capability via disco#info. + * Don't forget to use jabber_add_feature when supporting the sending of PEP events of this type. + * + * @parameter shortname A short name for this feature for XEP-0115. It has no semantic meaning, it just has to be unique. + * @parameter xmlns The namespace for this event + * @parameter handlerfunc The callback to be used when receiving an event with this namespace + */ +void jabber_pep_register_handler(const char *shortname, const char *xmlns, JabberPEPHandler handlerfunc); + +/* + * Request a specific item from another PEP node. + * + * @parameter js The JabberStream that should be used + * @parameter to The target PEP node + * @parameter node The node name of the item that is requested + * @parameter id The item id of the requested item (may be NULL) + * @parameter cb The callback to be used when this item is received + * + * The items element passed to the callback will be NULL if any error occured (like a permission error, node doesn't exist etc.) + */ +void jabber_pep_request_item(JabberStream *js, const char *to, const char *node, const char *id, JabberPEPHandler cb); + +/* + * Default callback that can be used for namespaces which should only be enabled when PEP is supported + * + * @parameter js The JabberStream struct for this connection + * @parameter shortname The namespace's shortname (for caps), ignored. + * @parameter namespace The namespace that's queried, ignored. + * + * @returns TRUE when PEP is enabled, FALSE otherwise + */ +gboolean jabber_pep_namespace_only_when_pep_enabled_cb(JabberStream *js, const gchar *shortname, const gchar *namespace); + +void jabber_handle_event(JabberMessage *jm); + +/* + * Publishes PEP item(s) + * + * @parameter js The JabberStream associated with the connection this event should be published + * @parameter publish The publish node. This could be for example <publish node='http://jabber.org/protocol/tune'/> with an <item/> as subnode + */ +void jabber_pep_publish(JabberStream *js, xmlnode *publish); + +#endif /* _PURPLE_JABBER_PEP_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/ping.c Wed Sep 12 19:11:38 2007 +0000 @@ -0,0 +1,80 @@ +/* + * purple - Jabber Protocol Plugin + * + * Copyright (C) 2003, Nathan Walp <faceprint@faceprint.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "internal.h" + +#include "debug.h" +#include "xmlnode.h" + +#include "jabber.h" +#include "ping.h" +#include "iq.h" + +void +jabber_ping_parse(JabberStream *js, xmlnode *packet) +{ + JabberIq *iq; + + purple_debug_info("jabber", "jabber_ping_parse\n"); + + iq = jabber_iq_new(js, JABBER_IQ_RESULT); + + xmlnode_set_attrib(iq->node, "to", xmlnode_get_attrib(packet, "from") ); + + jabber_iq_set_id(iq, xmlnode_get_attrib(packet, "id")); + + jabber_iq_send(iq); +} + +static void jabber_ping_result_cb(JabberStream *js, xmlnode *packet, + gpointer data) +{ + const char *type = xmlnode_get_attrib(packet, "type"); + + purple_debug_info("jabber", "jabber_ping_result_cb\n"); + if(type && !strcmp(type, "result")) { + purple_debug_info("jabber", "PONG!\n"); + } else { + purple_debug_info("jabber", "(not supported)\n"); + } +} + +gboolean jabber_ping_jid(PurpleConversation *conv, const char *jid) +{ + JabberIq *iq; + xmlnode *ping; + + purple_debug_info("jabber", "jabber_ping_jid\n"); + + iq = jabber_iq_new(conv->account->gc->proto_data, JABBER_IQ_GET); + xmlnode_set_attrib(iq->node, "to", jid); + + ping = xmlnode_new_child(iq->node, "ping"); + xmlnode_set_namespace(ping, "urn:xmpp:ping"); + + jabber_iq_set_callback(iq, jabber_ping_result_cb, NULL); + jabber_iq_send(iq); + + + + return TRUE; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/ping.h Wed Sep 12 19:11:38 2007 +0000 @@ -0,0 +1,35 @@ +/** + * @file ping.h utility functions + * + * purple + * + * Copyright (C) 2003, Nathan Walp <faceprint@faceprint.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef _PURPLE_JABBER_PING_H_ +#define _PURPLE_JABBER_PING_H_ + +#include "jabber.h" +#include "conversation.h" + +void jabber_ping_parse(JabberStream *js, + xmlnode *packet); + + +gboolean jabber_ping_jid(PurpleConversation *conv, const char *jid); + + +#endif /* _PURPLE_JABBER_PING_H_ */
--- a/libpurple/protocols/jabber/presence.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/jabber/presence.c Wed Sep 12 19:11:38 2007 +0000 @@ -15,7 +15,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */ #include "internal.h" @@ -36,6 +36,9 @@ #include "presence.h" #include "iq.h" #include "jutil.h" +#include "adhoccommands.h" + +#include "usertune.h" static void chats_send_presence_foreach(gpointer key, gpointer val, @@ -101,6 +104,9 @@ char *stripped = NULL; JabberBuddyState state; int priority; + const char *artist, *title, *source, *uri, *track; + int length; + gboolean allowBuzz; if(NULL == status) { PurplePresence *gpresence = purple_account_get_presence(account); @@ -127,28 +133,95 @@ } purple_status_to_jabber(status, &state, &stripped, &priority); - + + /* check for buzz support */ + allowBuzz = purple_status_get_attr_boolean(status,"buzz"); + /* changing the buzz state has to trigger a re-broadcasting of the presence for caps */ + +#define CHANGED(a,b) ((!a && b) || (a && a[0] == '\0' && b && b[0] != '\0') || \ + (a && !b) || (a && a[0] != '\0' && b && b[0] == '\0') || (a && b && strcmp(a,b))) + /* check if there are any differences to the <presence> and send them in that case */ + if (allowBuzz != js->allowBuzz || js->old_state != state || CHANGED(js->old_msg, stripped) || + js->old_priority != priority || CHANGED(js->old_avatarhash, js->avatar_hash)) { + js->allowBuzz = allowBuzz; + presence = jabber_presence_create_js(js, state, stripped, priority); - presence = jabber_presence_create(state, stripped, priority); - g_free(stripped); + if(js->avatar_hash) { + x = xmlnode_new_child(presence, "x"); + xmlnode_set_namespace(x, "vcard-temp:x:update"); + photo = xmlnode_new_child(x, "photo"); + xmlnode_insert_data(photo, js->avatar_hash, -1); + } + + jabber_send(js, presence); - if(js->avatar_hash) { - x = xmlnode_new_child(presence, "x"); - xmlnode_set_namespace(x, "vcard-temp:x:update"); - photo = xmlnode_new_child(x, "photo"); - xmlnode_insert_data(photo, js->avatar_hash, -1); + g_hash_table_foreach(js->chats, chats_send_presence_foreach, presence); + xmlnode_free(presence); + + /* update old values */ + + if(js->old_msg) + g_free(js->old_msg); + if(js->old_avatarhash) + g_free(js->old_avatarhash); + js->old_msg = g_strdup(stripped); + js->old_avatarhash = g_strdup(js->avatar_hash); + js->old_state = state; + js->old_priority = priority; + g_free(stripped); + } + + /* next, check if there are any changes to the tune values */ + artist = purple_status_get_attr_string(status, PURPLE_TUNE_ARTIST); + title = purple_status_get_attr_string(status, PURPLE_TUNE_TITLE); + source = purple_status_get_attr_string(status, PURPLE_TUNE_ALBUM); + uri = purple_status_get_attr_string(status, PURPLE_TUNE_URL); + track = purple_status_get_attr_string(status, PURPLE_TUNE_TRACK); + length = (!purple_status_get_attr_value(status, PURPLE_TUNE_TIME))?-1:purple_status_get_attr_int(status, PURPLE_TUNE_TIME); + + if(CHANGED(artist, js->old_artist) || CHANGED(title, js->old_title) || CHANGED(source, js->old_source) || + CHANGED(uri, js->old_uri) || CHANGED(track, js->old_track) || (length != js->old_length)) { + PurpleJabberTuneInfo tuneinfo = { + (char*)artist, + (char*)title, + (char*)source, + (char*)track, + length, + (char*)uri + }; + jabber_tune_set(js->gc, &tuneinfo); + + /* update old values */ + if(js->old_artist) + g_free(js->old_artist); + if(js->old_title) + g_free(js->old_title); + if(js->old_source) + g_free(js->old_source); + if(js->old_uri) + g_free(js->old_uri); + if(js->old_track) + g_free(js->old_track); + js->old_artist = g_strdup(artist); + js->old_title = g_strdup(title); + js->old_source = g_strdup(source); + js->old_uri = g_strdup(uri); + js->old_length = length; + js->old_track = g_strdup(track); } - jabber_send(js, presence); - - g_hash_table_foreach(js->chats, chats_send_presence_foreach, presence); - xmlnode_free(presence); +#undef CHANGED jabber_presence_fake_to_self(js, status); } xmlnode *jabber_presence_create(JabberBuddyState state, const char *msg, int priority) { + return jabber_presence_create_js(NULL, state, msg, priority); +} + +xmlnode *jabber_presence_create_js(JabberStream *js, JabberBuddyState state, const char *msg, int priority) +{ xmlnode *show, *status, *presence, *pri, *c; const char *show_string = NULL; @@ -183,7 +256,39 @@ xmlnode_set_namespace(c, "http://jabber.org/protocol/caps"); xmlnode_set_attrib(c, "node", CAPS0115_NODE); xmlnode_set_attrib(c, "ver", VERSION); - + + if(js != NULL) { + /* add the extensions */ + char extlist[1024]; + unsigned remaining = 1023; /* one less for the \0 */ + GList *feature; + + extlist[0] = '\0'; + for(feature = jabber_features; feature && remaining > 0; feature = feature->next) { + JabberFeature *feat = (JabberFeature*)feature->data; + unsigned featlen; + + if(feat->is_enabled != NULL && feat->is_enabled(js, feat->shortname, feat->namespace) == FALSE) + continue; /* skip this feature */ + + featlen = strlen(feat->shortname); + + /* cut off when we don't have any more space left in our buffer (too bad) */ + if(featlen > remaining) + break; + + strncat(extlist,feat->shortname,remaining); + remaining -= featlen; + if(feature->next) { /* no space at the end */ + strncat(extlist," ",remaining); + --remaining; + } + } + /* did we add anything? */ + if(remaining < 1023) + xmlnode_set_attrib(c, "ext", extlist); + } + return presence; } @@ -252,6 +357,35 @@ } } +typedef struct _JabberPresenceCapabilities { + JabberStream *js; + JabberBuddyResource *jbr; + char *from; +} JabberPresenceCapabilities; + +static void jabber_presence_set_capabilities(JabberCapsClientInfo *info, gpointer user_data) { + JabberPresenceCapabilities *userdata = user_data; + GList *iter; + + if(userdata->jbr->caps) + jabber_caps_free_clientinfo(userdata->jbr->caps); + userdata->jbr->caps = info; + + for(iter = info->features; iter; iter = g_list_next(iter)) { + if(!strcmp((const char*)iter->data, "http://jabber.org/protocol/commands")) { + JabberIq *iq = jabber_iq_new_query(userdata->js, JABBER_IQ_GET, "http://jabber.org/protocol/disco#items"); + xmlnode *query = xmlnode_get_child_with_namespace(iq->node,"query","http://jabber.org/protocol/disco#items"); + xmlnode_set_attrib(iq->node, "to", userdata->from); + xmlnode_set_attrib(query, "node", "http://jabber.org/protocol/commands"); + + jabber_iq_set_callback(iq, jabber_adhoc_disco_result_cb, NULL); + jabber_iq_send(iq); + break; + } + } + g_free(user_data); +} + void jabber_presence_parse(JabberStream *js, xmlnode *packet) { const char *from = xmlnode_get_attrib(packet, "from"); @@ -273,6 +407,7 @@ xmlnode *y; gboolean muc = FALSE; char *avatar_hash = NULL; + xmlnode *caps = NULL; if(!(jb = jabber_buddy_find(js, from, TRUE))) return; @@ -335,8 +470,10 @@ for(y = packet->child; y; y = y->next) { + const char *xmlns; if(y->type != XMLNODE_TYPE_TAG) continue; + xmlns = xmlnode_get_namespace(y); if(!strcmp(y->name, "status")) { g_free(status); @@ -347,6 +484,11 @@ priority = atoi(p); g_free(p); } + } else if(!strcmp(y->name, "delay") && !strcmp(xmlns, "urn:xmpp:delay")) { + /* XXX: compare the time. jabber:x:delay can happen on presence packets that aren't really and truly delayed */ + delayed = TRUE; + } else if(!strcmp(y->name, "c") && !strcmp(xmlns, "http://jabber.org/protocol/caps")) { + caps = y; /* store for later, when creating buddy resource */ } else if(!strcmp(y->name, "x")) { const char *xmlns = xmlnode_get_namespace(y); if(xmlns && !strcmp(xmlns, "jabber:x:delay")) { @@ -524,18 +666,22 @@ g_free(room_jid); } else { buddy_name = g_strdup_printf("%s%s%s", jid->node ? jid->node : "", - jid->node ? "@" : "", jid->domain); + jid->node ? "@" : "", jid->domain); if((b = purple_find_buddy(js->gc->account, buddy_name)) == NULL) { - purple_debug_warning("jabber", "Got presence for unknown buddy %s on account %s (%x)\n", - buddy_name, purple_account_get_username(js->gc->account), js->gc->account); - jabber_id_free(jid); - g_free(avatar_hash); - g_free(buddy_name); - g_free(status); - return; + if(!jid->node || strcmp(jid->node,js->user->node) || strcmp(jid->domain,js->user->domain)) { + purple_debug_warning("jabber", "Got presence for unknown buddy %s on account %s (%x)\n", + buddy_name, purple_account_get_username(js->gc->account), js->gc->account); + jabber_id_free(jid); + g_free(avatar_hash); + g_free(buddy_name); + g_free(status); + return; + } else { + /* this is a different resource of our own account. Resume even when this account isn't on our blist */ + } } - if(avatar_hash) { + if(b && avatar_hash) { const char *avatar_hash2 = purple_buddy_icons_get_checksum_for_user(b); if(!avatar_hash2 || strcmp(avatar_hash, avatar_hash2)) { JabberIq *iq; @@ -573,6 +719,19 @@ } else { jbr = jabber_buddy_track_resource(jb, jid->resource, priority, state, status); + if(caps) { + const char *node = xmlnode_get_attrib(caps,"node"); + const char *ver = xmlnode_get_attrib(caps,"ver"); + const char *ext = xmlnode_get_attrib(caps,"ext"); + + if(node && ver) { + JabberPresenceCapabilities *userdata = g_new0(JabberPresenceCapabilities, 1); + userdata->js = js; + userdata->jbr = jbr; + userdata->from = g_strdup(from); + jabber_caps_get_info(js, from, node, ver, ext, jabber_presence_set_capabilities, userdata); + } + } } if((found_jbr = jabber_buddy_find_resource(jb, NULL))) {
--- a/libpurple/protocols/jabber/presence.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/jabber/presence.h Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _PURPLE_JABBER_PRESENCE_H_ #define _PURPLE_JABBER_PRESENCE_H_ @@ -27,7 +27,8 @@ #include "xmlnode.h" void jabber_presence_send(PurpleAccount *account, PurpleStatus *status); -xmlnode *jabber_presence_create(JabberBuddyState state, const char *msg, int priority); +xmlnode *jabber_presence_create(JabberBuddyState state, const char *msg, int priority); /* DEPRECATED */ +xmlnode *jabber_presence_create_js(JabberStream *js, JabberBuddyState state, const char *msg, int priority); void jabber_presence_parse(JabberStream *js, xmlnode *packet); void jabber_presence_subscription_set(JabberStream *js, const char *who, const char *type);
--- a/libpurple/protocols/jabber/roster.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/jabber/roster.c Wed Sep 12 19:11:38 2007 +0000 @@ -15,7 +15,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */ #include "internal.h" @@ -58,6 +58,7 @@ { GSList *buddies, *g2, *l; gchar *my_bare_jid; + GList *pool = NULL; buddies = purple_find_buddies(js->gc->account, jid); @@ -89,13 +90,20 @@ g_free(l->data); g2 = g_slist_delete_link(g2, l); } else { - purple_blist_remove_buddy(b); + pool = g_list_prepend(pool, b); } } while(g2) { - PurpleBuddy *b = purple_buddy_new(js->gc->account, jid, alias); PurpleGroup *g = purple_find_group(g2->data); + PurpleBuddy *b = NULL; + + if (pool) { + b = pool->data; + pool = g_list_delete_link(pool, pool); + } else { + b = purple_buddy_new(js->gc->account, jid, alias); + } if(!g) { g = purple_group_new(g2->data); @@ -121,6 +129,12 @@ g2 = g_slist_delete_link(g2, g2); } + while (pool) { + PurpleBuddy *b = pool->data; + purple_blist_remove_buddy(b); + pool = g_list_delete_link(pool, pool); + } + g_free(my_bare_jid); g_slist_free(buddies); }
--- a/libpurple/protocols/jabber/roster.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/jabber/roster.h Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _PURPLE_JABBER_ROSTER_H_ #define _PURPLE_JABBER_ROSTER_H_
--- a/libpurple/protocols/jabber/si.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/jabber/si.c Wed Sep 12 19:11:38 2007 +0000 @@ -16,7 +16,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */ @@ -708,6 +708,10 @@ jabber_iq_set_callback(iq, jabber_si_xfer_send_method_cb, xfer); + /* Store the IQ id so that we can cancel the callback */ + g_free(jsx->iq_id); + jsx->iq_id = g_strdup(iq->id); + jabber_iq_send(iq); } @@ -722,6 +726,8 @@ purple_proxy_connect_cancel(jsx->connect_data); if (jsx->listen_data != NULL) purple_network_listen_cancel(jsx->listen_data); + if (jsx->iq_id != NULL) + jabber_iq_remove_callback_by_id(js, jsx->iq_id); g_free(jsx->stream_id); g_free(jsx->iq_id);
--- a/libpurple/protocols/jabber/si.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/jabber/si.h Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _PURPLE_JABBER_SI_H_ #define _PURPLE_JABBER_SI_H_
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/usermood.c Wed Sep 12 19:11:38 2007 +0000 @@ -0,0 +1,215 @@ +/* + * purple - Jabber Protocol Plugin + * + * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "internal.h" + +#include "usermood.h" +#include "pep.h" +#include <assert.h> +#include <string.h> +#include "internal.h" +#include "request.h" + +static const char *moodstrings[] = { + "afraid", + "amazed", + "angry", + "annoyed", + "anxious", + "aroused", + "ashamed", + "bored", + "brave", + "calm", + "cold", + "confused", + "contented", + "cranky", + "curious", + "depressed", + "disappointed", + "disgusted", + "distracted", + "embarrassed", + "excited", + "flirtatious", + "frustrated", + "grumpy", + "guilty", + "happy", + "hot", + "humbled", + "humiliated", + "hungry", + "hurt", + "impressed", + "in_awe", + "in_love", + "indignant", + "interested", + "intoxicated", + "invincible", + "jealous", + "lonely", + "mean", + "moody", + "nervous", + "neutral", + "offended", + "playful", + "proud", + "relieved", + "remorseful", + "restless", + "sad", + "sarcastic", + "serious", + "shocked", + "shy", + "sick", + "sleepy", + "stressed", + "surprised", + "thirsty", + "worried", + NULL +}; + +static void jabber_mood_cb(JabberStream *js, const char *from, xmlnode *items) { + /* it doesn't make sense to have more than one item here, so let's just pick the first one */ + xmlnode *item = xmlnode_get_child(items, "item"); + const char *newmood = NULL; + char *moodtext = NULL; + JabberBuddy *buddy = jabber_buddy_find(js, from, FALSE); + xmlnode *moodinfo, *mood; + /* ignore the mood of people not on our buddy list */ + if (!buddy || !item) + return; + + mood = xmlnode_get_child_with_namespace(item, "mood", "http://jabber.org/protocol/mood"); + if (!mood) + return; + for (moodinfo = mood->child; moodinfo; moodinfo = moodinfo->next) { + if (moodinfo->type == XMLNODE_TYPE_TAG) { + if (!strcmp(moodinfo->name, "text")) { + if (!moodtext) /* only pick the first one */ + moodtext = xmlnode_get_data(moodinfo); + } else { + int i; + for (i = 0; moodstrings[i]; ++i) { + /* verify that the mood is known (valid) */ + if (!strcmp(moodinfo->name, moodstrings[i])) { + newmood = moodstrings[i]; + break; + } + } + } + if (newmood != NULL && moodtext != NULL) + break; + } + } + if (newmood != NULL) { + const char *status_id; + JabberBuddyResource *resource = jabber_buddy_find_resource(buddy, NULL); + if(!resource) { /* huh? */ + g_free(moodtext); + return; + } + status_id = jabber_buddy_state_get_status_id(resource->state); + + purple_prpl_got_user_status(js->gc->account, from, status_id, "mood", _(newmood), "moodtext", moodtext?moodtext:"", NULL); + } + g_free(moodtext); +} + +void jabber_mood_init(void) { + jabber_add_feature("mood", "http://jabber.org/protocol/mood", jabber_pep_namespace_only_when_pep_enabled_cb); + jabber_pep_register_handler("moodn", "http://jabber.org/protocol/mood", jabber_mood_cb); +} + +static void do_mood_set_from_fields(PurpleConnection *gc, PurpleRequestFields *fields) { + JabberStream *js = gc->proto_data; + + jabber_mood_set(js, moodstrings[purple_request_fields_get_choice(fields, "mood")], purple_request_fields_get_string(fields, "text")); +} + +static void do_mood_set_mood(PurplePluginAction *action) { + PurpleConnection *gc = (PurpleConnection *) action->context; + + PurpleRequestFields *fields; + PurpleRequestFieldGroup *group; + PurpleRequestField *field; + int i; + + fields = purple_request_fields_new(); + group = purple_request_field_group_new(NULL); + purple_request_fields_add_group(fields, group); + + field = purple_request_field_choice_new("mood", + _("Mood"), 0); + + for(i = 0; moodstrings[i]; ++i) + purple_request_field_choice_add(field, _(moodstrings[i])); + + purple_request_field_set_required(field, TRUE); + purple_request_field_group_add_field(group, field); + + field = purple_request_field_string_new("text", + _("Description"), NULL, + FALSE); + purple_request_field_group_add_field(group, field); + + purple_request_fields(gc, _("Edit User Mood"), + _("Edit User Mood"), + _("Please select your mood from the list."), + fields, + _("Set"), G_CALLBACK(do_mood_set_from_fields), + _("Cancel"), NULL, + purple_connection_get_account(gc), NULL, NULL, + gc); + +} + +void jabber_mood_init_action(GList **m) { + PurplePluginAction *act = purple_plugin_action_new(_("Set Mood..."), do_mood_set_mood); + *m = g_list_append(*m, act); +} + +void jabber_mood_set(JabberStream *js, const char *mood, const char *text) { + xmlnode *publish, *moodnode; + + assert(mood != NULL); + + publish = xmlnode_new("publish"); + xmlnode_set_attrib(publish,"node","http://jabber.org/protocol/mood"); + moodnode = xmlnode_new_child(xmlnode_new_child(publish, "item"), "mood"); + xmlnode_set_namespace(moodnode, "http://jabber.org/protocol/mood"); + xmlnode_new_child(moodnode, mood); + + if (text && text[0] != '\0') { + xmlnode *textnode = xmlnode_new_child(moodnode, "text"); + xmlnode_insert_data(textnode, text, -1); + } + + jabber_pep_publish(js, publish); + /* publish is freed by jabber_pep_publish -> jabber_iq_send -> jabber_iq_free + (yay for well-defined memory management rules) */ +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/usermood.h Wed Sep 12 19:11:38 2007 +0000 @@ -0,0 +1,37 @@ +/* + * purple - Jabber Protocol Plugin + * + * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _PURPLE_JABBER_USERMOOD_H_ +#define _PURPLE_JABBER_USERMOOD_H_ + +#include "jabber.h" + +/* Implementation of XEP-0107 */ + +void jabber_mood_init(void); + +void jabber_mood_init_action(GList **m); + +void jabber_mood_set(JabberStream *js, + const char *mood, /* must be one of the valid strings defined in the XEP */ + const char *text /* might be NULL */); + +#endif /* _PURPLE_JABBER_USERMOOD_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/usernick.c Wed Sep 12 19:11:38 2007 +0000 @@ -0,0 +1,102 @@ +/* + * purple - Jabber Protocol Plugin + * + * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "internal.h" + +#include "usernick.h" +#include "pep.h" +#include <assert.h> +#include <string.h> +#include "internal.h" +#include "request.h" +#include "status.h" + +static void jabber_nick_cb(JabberStream *js, const char *from, xmlnode *items) { + /* it doesn't make sense to have more than one item here, so let's just pick the first one */ + xmlnode *item = xmlnode_get_child(items, "item"); + JabberBuddy *buddy = jabber_buddy_find(js, from, FALSE); + xmlnode *nick; + const char *nickname = NULL; + + /* ignore the tune of people not on our buddy list */ + if (!buddy || !item) + return; + + nick = xmlnode_get_child_with_namespace(item, "nick", "http://jabber.org/protocol/nick"); + if (!nick) + return; + nickname = xmlnode_get_data(nick); + + serv_got_alias(js->gc, from, nickname); +} + +static void do_nick_set(JabberStream *js, const char *nick) { + xmlnode *publish, *nicknode; + + publish = xmlnode_new("publish"); + xmlnode_set_attrib(publish,"node","http://jabber.org/protocol/nick"); + nicknode = xmlnode_new_child(xmlnode_new_child(publish, "item"), "nick"); + xmlnode_set_namespace(nicknode, "http://jabber.org/protocol/nick"); + + if(nick && nick[0] != '\0') + xmlnode_insert_data(nicknode, nick, -1); + + jabber_pep_publish(js, publish); + /* publish is freed by jabber_pep_publish -> jabber_iq_send -> jabber_iq_free + (yay for well-defined memory management rules) */ +} + +static void do_nick_got_own_nick_cb(JabberStream *js, const char *from, xmlnode *items) { + const char *oldnickname = NULL; + xmlnode *item = xmlnode_get_child(items,"item"); + + if(item) { + xmlnode *nick = xmlnode_get_child_with_namespace(item,"nick","http://jabber.org/protocol/nick"); + if(nick) + oldnickname = xmlnode_get_data(nick); + } + + purple_request_input(js->gc, _("Set User Nickname"), _("Please specify a new nickname for you."), + _("This information is visible to all contacts on your contact list, so choose something appropriate."), + oldnickname, FALSE, FALSE, NULL, _("Set"), PURPLE_CALLBACK(do_nick_set), _("Cancel"), NULL, + purple_connection_get_account(js->gc), NULL, NULL, js); +} + +static void do_nick_set_nick(PurplePluginAction *action) { + PurpleConnection *gc = (PurpleConnection *) action->context; + JabberStream *js = gc->proto_data; + char *jid = g_strdup_printf("%s@%s", js->user->node, js->user->domain); + + /* since the nickname might have been changed by another resource of this account, we always have to request the old one + from the server to present as the default for the new one */ + jabber_pep_request_item(js, jid, "http://jabber.org/protocol/nick", NULL, do_nick_got_own_nick_cb); + g_free(jid); +} + +void jabber_nick_init(void) { + jabber_add_feature("nick", "http://jabber.org/protocol/nick", jabber_pep_namespace_only_when_pep_enabled_cb); + jabber_pep_register_handler("nickn", "http://jabber.org/protocol/nick", jabber_nick_cb); +} + +void jabber_nick_init_action(GList **m) { + PurplePluginAction *act = purple_plugin_action_new(_("Set Nickname..."), do_nick_set_nick); + *m = g_list_append(*m, act); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/usernick.h Wed Sep 12 19:11:38 2007 +0000 @@ -0,0 +1,32 @@ +/* + * purple - Jabber Protocol Plugin + * + * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _PURPLE_JABBER_USERNICK_H_ +#define _PURPLE_JABBER_USERNICK_H_ + +#include "jabber.h" + +/* Implementation of XEP-0172 */ + +void jabber_nick_init(void); +void jabber_nick_init_action(GList **m); + +#endif /* _PURPLE_JABBER_USERNICK_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/usertune.c Wed Sep 12 19:11:38 2007 +0000 @@ -0,0 +1,124 @@ +/* + * purple - Jabber Protocol Plugin + * + * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "internal.h" + +#include "usertune.h" +#include "pep.h" +#include <assert.h> +#include <string.h> +#include "internal.h" +#include "request.h" +#include "status.h" + +static void jabber_tune_cb(JabberStream *js, const char *from, xmlnode *items) { + /* it doesn't make sense to have more than one item here, so let's just pick the first one */ + xmlnode *item = xmlnode_get_child(items, "item"); + JabberBuddy *buddy = jabber_buddy_find(js, from, FALSE); + xmlnode *tuneinfo, *tune; + PurpleJabberTuneInfo tuneinfodata; + JabberBuddyResource *resource; + const char *status_id; + + /* ignore the tune of people not on our buddy list */ + if (!buddy || !item) + return; + + tuneinfodata.artist = ""; + tuneinfodata.title = ""; + tuneinfodata.album = ""; + tuneinfodata.track = ""; + tuneinfodata.time = -1; + tuneinfodata.url = ""; + + tune = xmlnode_get_child_with_namespace(item, "tune", "http://jabber.org/protocol/tune"); + if (!tune) + return; + for (tuneinfo = tune->child; tuneinfo; tuneinfo = tuneinfo->next) { + if (tuneinfo->type == XMLNODE_TYPE_TAG) { + if (!strcmp(tuneinfo->name, "artist")) { + if (tuneinfodata.artist[0] == '\0') /* only pick the first one */ + tuneinfodata.artist = xmlnode_get_data(tuneinfo); + } else if (!strcmp(tuneinfo->name, "length")) { + if (tuneinfodata.time == -1) { + char *length = xmlnode_get_data(tuneinfo); + if (length) + tuneinfodata.time = strtol(length, NULL, 10); + } + } else if (!strcmp(tuneinfo->name, "source")) { + if (tuneinfodata.album[0] == '\0') /* only pick the first one */ + tuneinfodata.album = xmlnode_get_data(tuneinfo); + } else if (!strcmp(tuneinfo->name, "title")) { + if (tuneinfodata.title[0] == '\0') /* only pick the first one */ + tuneinfodata.title = xmlnode_get_data(tuneinfo); + } else if (!strcmp(tuneinfo->name, "track")) { + if (tuneinfodata.track[0] == '\0') /* only pick the first one */ + tuneinfodata.track = xmlnode_get_data(tuneinfo); + } else if (!strcmp(tuneinfo->name, "uri")) { + if (tuneinfodata.url[0] == '\0') /* only pick the first one */ + tuneinfodata.url = xmlnode_get_data(tuneinfo); + } + } + } + resource = jabber_buddy_find_resource(buddy, NULL); + if(!resource) + return; /* huh? */ + status_id = jabber_buddy_state_get_status_id(resource->state); + + purple_prpl_got_user_status(js->gc->account, from, status_id, PURPLE_TUNE_ARTIST, tuneinfodata.artist, PURPLE_TUNE_TITLE, tuneinfodata.title, PURPLE_TUNE_ALBUM, tuneinfodata.album, PURPLE_TUNE_TRACK, tuneinfodata.track, PURPLE_TUNE_TIME, tuneinfodata.time, PURPLE_TUNE_URL, tuneinfodata.url, NULL); +} + +void jabber_tune_init(void) { + jabber_add_feature("tune", "http://jabber.org/protocol/tune", jabber_pep_namespace_only_when_pep_enabled_cb); + jabber_pep_register_handler("tunen", "http://jabber.org/protocol/tune", jabber_tune_cb); +} + +void jabber_tune_set(PurpleConnection *gc, const PurpleJabberTuneInfo *tuneinfo) { + xmlnode *publish, *tunenode; + JabberStream *js = gc->proto_data; + + publish = xmlnode_new("publish"); + xmlnode_set_attrib(publish,"node","http://jabber.org/protocol/tune"); + tunenode = xmlnode_new_child(xmlnode_new_child(publish, "item"), "tune"); + xmlnode_set_namespace(tunenode, "http://jabber.org/protocol/tune"); + + if(tuneinfo) { + if(tuneinfo->artist && tuneinfo->artist[0] != '\0') + xmlnode_insert_data(xmlnode_new_child(tunenode, "artist"),tuneinfo->artist,-1); + if(tuneinfo->title && tuneinfo->title[0] != '\0') + xmlnode_insert_data(xmlnode_new_child(tunenode, "title"),tuneinfo->title,-1); + if(tuneinfo->album && tuneinfo->album[0] != '\0') + xmlnode_insert_data(xmlnode_new_child(tunenode, "source"),tuneinfo->album,-1); + if(tuneinfo->url && tuneinfo->url[0] != '\0') + xmlnode_insert_data(xmlnode_new_child(tunenode, "uri"),tuneinfo->url,-1); + if(tuneinfo->time >= 0) { + char *length = g_strdup_printf("%d", tuneinfo->time); + xmlnode_insert_data(xmlnode_new_child(tunenode, "length"),length,-1); + g_free(length); + } + if(tuneinfo->track && tuneinfo->track[0] != '\0') + xmlnode_insert_data(xmlnode_new_child(tunenode, "track"),tuneinfo->track,-1); + } + + jabber_pep_publish(js, publish); + /* publish is freed by jabber_pep_publish -> jabber_iq_send -> jabber_iq_free + (yay for well-defined memory management rules) */ +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/usertune.h Wed Sep 12 19:11:38 2007 +0000 @@ -0,0 +1,43 @@ +/* + * purple - Jabber Protocol Plugin + * + * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _PURPLE_JABBER_USERTUNE_H_ +#define _PURPLE_JABBER_USERTUNE_H_ + +#include "jabber.h" + +/* Implementation of XEP-0118 */ + +typedef struct _PurpleJabberTuneInfo PurpleJabberTuneInfo; +struct _PurpleJabberTuneInfo { + char *artist; + char *title; + char *album; + char *track; /* either the index of the track in the album or the URL for a stream */ + int time; /* in seconds, -1 for unknown */ + char *url; +}; + +void jabber_tune_init(void); + +void jabber_tune_set(PurpleConnection *gc, const PurpleJabberTuneInfo *tuneinfo); + +#endif /* _PURPLE_JABBER_USERTUNE_H_ */
--- a/libpurple/protocols/jabber/win32/posix.uname.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/jabber/win32/posix.uname.c Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, - Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA. */ /*
--- a/libpurple/protocols/jabber/xdata.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/jabber/xdata.c Wed Sep 12 19:11:38 2007 +0000 @@ -15,7 +15,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */ #include "internal.h" @@ -37,22 +37,39 @@ struct jabber_x_data_data { GHashTable *fields; GSList *values; - jabber_x_data_cb cb; + jabber_x_data_action_cb cb; gpointer user_data; JabberStream *js; + GList *actions; + PurpleRequestFieldGroup *actiongroup; }; static void jabber_x_data_ok_cb(struct jabber_x_data_data *data, PurpleRequestFields *fields) { xmlnode *result = xmlnode_new("x"); - jabber_x_data_cb cb = data->cb; + jabber_x_data_action_cb cb = data->cb; gpointer user_data = data->user_data; JabberStream *js = data->js; GList *groups, *flds; + char *actionhandle = NULL; + gboolean hasActions = (data->actions != NULL); xmlnode_set_namespace(result, "jabber:x:data"); xmlnode_set_attrib(result, "type", "submit"); for(groups = purple_request_fields_get_groups(fields); groups; groups = groups->next) { + if(groups->data == data->actiongroup) { + for(flds = purple_request_field_group_get_fields(groups->data); flds; flds = flds->next) { + PurpleRequestField *field = flds->data; + const char *id = purple_request_field_get_id(field); + int handleindex; + if(strcmp(id, "libpurple:jabber:xdata:actions")) + continue; + handleindex = purple_request_field_choice_get_value(field); + actionhandle = g_strdup(g_list_nth_data(data->actions, handleindex)); + break; + } + continue; + } for(flds = purple_request_field_group_get_fields(groups->data); flds; flds = flds->next) { xmlnode *fieldnode, *valuenode; PurpleRequestField *field = flds->data; @@ -127,31 +144,59 @@ g_free(data->values->data); data->values = g_slist_delete_link(data->values, data->values); } + if (data->actions) { + GList *action; + for(action = data->actions; action; action = g_list_next(action)) { + g_free(action->data); + } + g_list_free(data->actions); + } g_free(data); - cb(js, result, user_data); + if (hasActions) { + cb(js, result, actionhandle, user_data); + g_free(actionhandle); + } else + ((jabber_x_data_cb)cb)(js, result, user_data); } static void jabber_x_data_cancel_cb(struct jabber_x_data_data *data, PurpleRequestFields *fields) { xmlnode *result = xmlnode_new("x"); - jabber_x_data_cb cb = data->cb; + jabber_x_data_action_cb cb = data->cb; gpointer user_data = data->user_data; JabberStream *js = data->js; + gboolean hasActions = FALSE; g_hash_table_destroy(data->fields); while(data->values) { g_free(data->values->data); data->values = g_slist_delete_link(data->values, data->values); } + if (data->actions) { + GList *action; + hasActions = TRUE; + for(action = data->actions; action; action = g_list_next(action)) { + g_free(action->data); + } + g_list_free(data->actions); + } g_free(data); xmlnode_set_namespace(result, "jabber:x:data"); xmlnode_set_attrib(result, "type", "cancel"); - cb(js, result, user_data); + if (hasActions) + cb(js, result, NULL, user_data); + else + ((jabber_x_data_cb)cb)(js, result, user_data); } void *jabber_x_data_request(JabberStream *js, xmlnode *packet, jabber_x_data_cb cb, gpointer user_data) { + return jabber_x_data_request_with_actions(js, packet, NULL, 0, (jabber_x_data_action_cb)cb, user_data); +} + +void *jabber_x_data_request_with_actions(JabberStream *js, xmlnode *packet, GList *actions, int defaultaction, jabber_x_data_action_cb cb, gpointer user_data) +{ void *handle; xmlnode *fn, *x; PurpleRequestFields *fields; @@ -180,7 +225,7 @@ char *value = NULL; if(!type) - continue; + type = "text-single"; if(!var && strcmp(type, "fixed")) continue; @@ -191,8 +236,6 @@ value = xmlnode_get_data(valuenode); - /* XXX: handle <required/> */ - if(!strcmp(type, "text-private")) { if((valuenode = xmlnode_get_child(fn, "value"))) value = xmlnode_get_data(valuenode); @@ -324,6 +367,26 @@ g_free(value); } + + if(field && xmlnode_get_child(fn, "required")) + purple_request_field_set_required(field,TRUE); + } + + if(actions != NULL) { + PurpleRequestField *actionfield; + GList *action; + data->actiongroup = group = purple_request_field_group_new(_("Actions")); + purple_request_fields_add_group(fields, group); + actionfield = purple_request_field_choice_new("libpurple:jabber:xdata:actions", _("Select an action"), defaultaction); + + for(action = actions; action; action = g_list_next(action)) { + JabberXDataAction *a = action->data; + + purple_request_field_choice_add(actionfield, a->name); + data->actions = g_list_append(data->actions, g_strdup(a->handle)); + } + purple_request_field_set_required(actionfield,TRUE); + purple_request_field_group_add_field(group, actionfield); } if((x = xmlnode_get_child(packet, "title")))
--- a/libpurple/protocols/jabber/xdata.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/jabber/xdata.h Wed Sep 12 19:11:38 2007 +0000 @@ -17,7 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _PURPLE_JABBER_XDATA_H_ #define _PURPLE_JABBER_XDATA_H_ @@ -25,7 +25,14 @@ #include "jabber.h" #include "xmlnode.h" +typedef struct _JabberXDataAction { + char *name; + char *handle; +} JabberXDataAction; + typedef void (*jabber_x_data_cb)(JabberStream *js, xmlnode *result, gpointer user_data); +typedef void (*jabber_x_data_action_cb)(JabberStream *js, xmlnode *result, const char *actionhandle, gpointer user_data); void *jabber_x_data_request(JabberStream *js, xmlnode *packet, jabber_x_data_cb cb, gpointer user_data); +void *jabber_x_data_request_with_actions(JabberStream *js, xmlnode *packet, GList *actions, int defaultaction, jabber_x_data_action_cb cb, gpointer user_data); #endif /* _PURPLE_JABBER_XDATA_H_ */
--- a/libpurple/protocols/msn/cmdproc.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/cmdproc.c Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "msn.h" #include "cmdproc.h"
--- a/libpurple/protocols/msn/cmdproc.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/cmdproc.h Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _MSN_CMDPROC_H_ #define _MSN_CMDPROC_H_
--- a/libpurple/protocols/msn/command.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/command.c Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "msn.h" #include "command.h"
--- a/libpurple/protocols/msn/command.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/command.h Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _MSN_COMMAND_H #define _MSN_COMMAND_H
--- a/libpurple/protocols/msn/dialog.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/dialog.c Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "msn.h"
--- a/libpurple/protocols/msn/dialog.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/dialog.h Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _MSN_DIALOG_H_ #define _MSN_DIALOG_H_
--- a/libpurple/protocols/msn/directconn.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/directconn.c Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "msn.h" #include "directconn.h"
--- a/libpurple/protocols/msn/directconn.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/directconn.h Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _MSN_DIRECTCONN_H_ #define _MSN_DIRECTCONN_H_
--- a/libpurple/protocols/msn/error.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/error.c Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "msn.h" #include "error.h"
--- a/libpurple/protocols/msn/error.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/error.h Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _MSN_ERROR_H_ #define _MSN_ERROR_H_
--- a/libpurple/protocols/msn/group.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/group.c Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "msn.h" #include "group.h"
--- a/libpurple/protocols/msn/group.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/group.h Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _MSN_GROUP_H_ #define _MSN_GROUP_H_
--- a/libpurple/protocols/msn/history.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/history.c Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "msn.h" #include "history.h"
--- a/libpurple/protocols/msn/history.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/history.h Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _MSN_HISTORY_H #define _MSN_HISTORY_H
--- a/libpurple/protocols/msn/httpconn.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/httpconn.c Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "msn.h" #include "debug.h" @@ -181,7 +181,7 @@ { msn_session_set_error(httpconn->session, MSN_ERROR_HTTP_MALFORMED, NULL); - purple_debug_error("msn", "Malformed X-MSN-Messenger field.\n{%s}", + purple_debug_error("msn", "Malformed X-MSN-Messenger field.\n{%s}\n", buf); g_free(body);
--- a/libpurple/protocols/msn/httpconn.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/httpconn.h Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _MSN_HTTPCONN_H_ #define _MSN_HTTPCONN_H_
--- a/libpurple/protocols/msn/msg.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/msg.c Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "msn.h" #include "msg.h" @@ -400,7 +400,7 @@ GList *l; char *n, *base, *end; int len; - size_t body_len; + size_t body_len = 0; const void *body; g_return_val_if_fail(msg != NULL, NULL);
--- a/libpurple/protocols/msn/msg.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/msg.h Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _MSN_MSG_H_ #define _MSN_MSG_H_
--- a/libpurple/protocols/msn/msn-utils.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/msn-utils.c Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "msn.h" #include "msn-utils.h"
--- a/libpurple/protocols/msn/msn-utils.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/msn-utils.h Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _MSN_UTILS_H_ #define _MSN_UTILS_H_
--- a/libpurple/protocols/msn/msn.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/msn.c Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #define PHOTO_SUPPORT 1 @@ -100,25 +100,53 @@ return buf; } -static PurpleCmdRet -msn_cmd_nudge(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, void *data) +static gboolean +msn_send_attention(PurpleConnection *gc, const char *username, guint type) { - PurpleAccount *account = purple_conversation_get_account(conv); - PurpleConnection *gc = purple_account_get_connection(account); MsnMessage *msg; MsnSession *session; MsnSwitchBoard *swboard; msg = msn_message_new_nudge(); session = gc->proto_data; - swboard = msn_session_get_swboard(session, purple_conversation_get_name(conv), MSN_SB_FLAG_IM); + swboard = msn_session_get_swboard(session, username, MSN_SB_FLAG_IM); if (swboard == NULL) - return PURPLE_CMD_RET_FAILED; + return FALSE; msn_switchboard_send_msg(swboard, msg, TRUE); - purple_conversation_write(conv, NULL, _("You have just sent a Nudge!"), PURPLE_MESSAGE_SYSTEM, time(NULL)); + return TRUE; +} + +static GList * +msn_attention_types(PurpleAccount *account) +{ + PurpleAttentionType *attn; + static GList *list = NULL; + + if (!list) { + attn = g_new0(PurpleAttentionType, 1); + attn->name = _("Nudge"); + attn->incoming_description = _("%s has nudged you!"); + attn->outgoing_description = _("Nudging %s..."); + list = g_list_append(list, attn); + } + + return list; +} + + +static PurpleCmdRet +msn_cmd_nudge(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, void *data) +{ + PurpleAccount *account = purple_conversation_get_account(conv); + PurpleConnection *gc = purple_account_get_connection(account); + const gchar *username; + + username = purple_conversation_get_name(conv); + + serv_send_attention(gc, username, MSN_NUDGE); return PURPLE_CMD_RET_OK; } @@ -2101,11 +2129,11 @@ NULL, /* whiteboard_prpl_ops */ NULL, /* send_raw */ NULL, /* roomlist_room_serialize */ + NULL, /* unregister_user */ + msn_send_attention, /* send_attention */ + msn_attention_types, /* attention_types */ /* padding */ - NULL, - NULL, - NULL, NULL };
--- a/libpurple/protocols/msn/msn.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/msn.h Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _MSN_H_ #define _MSN_H_ @@ -79,6 +79,8 @@ "Client-Name: Purple/" VERSION "\r\n" \ "Chat-Logging: Y\r\n" +/* Index into attention_types */ +#define MSN_NUDGE 0 typedef enum {
--- a/libpurple/protocols/msn/nexus.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/nexus.c Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "msn.h" #include "nexus.h" @@ -188,7 +188,7 @@ purple_ssl_close(nexus->gsc); nexus->gsc = NULL; - purple_debug_misc("msn", "ssl buffer: {%s}", nexus->read_buf); + purple_debug_misc("msn", "ssl buffer: {%s}\n", nexus->read_buf); if (strstr(nexus->read_buf, "HTTP/1.1 302") != NULL) {
--- a/libpurple/protocols/msn/nexus.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/nexus.h Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _MSN_NEXUS_H_ #define _MSN_NEXUS_H_
--- a/libpurple/protocols/msn/notification.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/notification.c Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "msn.h" #include "notification.h"
--- a/libpurple/protocols/msn/notification.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/notification.h Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _MSN_NOTIFICATION_H_ #define _MSN_NOTIFICATION_H_
--- a/libpurple/protocols/msn/object.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/object.c Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "object.h" #include "debug.h"
--- a/libpurple/protocols/msn/object.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/object.h Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _MSN_OBJECT_H_ #define _MSN_OBJECT_H_
--- a/libpurple/protocols/msn/page.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/page.c Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "msn.h" #include "page.h"
--- a/libpurple/protocols/msn/page.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/page.h Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _MSN_PAGE_H_ #define _MSN_PAGE_H_
--- a/libpurple/protocols/msn/servconn.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/servconn.c Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "msn.h" #include "servconn.h" @@ -51,7 +51,7 @@ servconn->num = session->servconns_count++; servconn->tx_buf = purple_circ_buffer_new(MSN_BUF_LEN); - servconn->tx_handler = -1; + servconn->tx_handler = 0; return servconn; } @@ -303,7 +303,7 @@ if (writelen == 0) { purple_input_remove(servconn->tx_handler); - servconn->tx_handler = -1; + servconn->tx_handler = 0; return; } @@ -328,7 +328,7 @@ if (!servconn->session->http_method) { - if (servconn->tx_handler == -1) { + if (servconn->tx_handler == 0) { switch (servconn->type) { case MSN_SERVCONN_NS: @@ -353,7 +353,7 @@ if (ret < 0 && errno == EAGAIN) ret = 0; if (ret >= 0 && ret < len) { - if (servconn->tx_handler == -1) + if (servconn->tx_handler == 0) servconn->tx_handler = purple_input_add( servconn->fd, PURPLE_INPUT_WRITE, servconn_write_cb, servconn);
--- a/libpurple/protocols/msn/servconn.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/servconn.h Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _MSN_SERVCONN_H_ #define _MSN_SERVCONN_H_
--- a/libpurple/protocols/msn/session.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/session.c Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "msn.h" #include "session.h"
--- a/libpurple/protocols/msn/session.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/session.h Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _MSN_SESSION_H_ #define _MSN_SESSION_H_
--- a/libpurple/protocols/msn/slp.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/slp.c Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "msn.h" #include "slp.h" @@ -343,7 +343,7 @@ if (xfer) { bin = (char *)purple_base64_decode(context, &bin_len); - file_size = GUINT32_FROM_LE(*((gsize *)bin + 2)); + file_size = GUINT32_FROM_LE(*(gsize *)(bin + 2)); uni_name = (gunichar2 *)(bin + 20); while(*uni_name != 0 && ((char *)uni_name - (bin + 20)) < MAX_FILE_NAME_LEN) {
--- a/libpurple/protocols/msn/slp.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/slp.h Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _MSN_SLP_H_ #define _MSN_SLP_H_
--- a/libpurple/protocols/msn/slpcall.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/slpcall.c Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "msn.h" #include "slpcall.h"
--- a/libpurple/protocols/msn/slpcall.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/slpcall.h Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _MSN_SLPCALL_H_ #define _MSN_SLPCALL_H_
--- a/libpurple/protocols/msn/slplink.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/slplink.c Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "msn.h" #include "slplink.h" @@ -49,7 +49,7 @@ tf = g_fopen(tmp, "wb"); if (tf == NULL) { - purple_debug_error("msn", "could not open debug file"); + purple_debug_error("msn", "could not open debug file\n"); return; } pload = msn_message_gen_payload(msg, &pload_size);
--- a/libpurple/protocols/msn/slplink.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/slplink.h Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _MSN_SLPLINK_H_ #define _MSN_SLPLINK_H_
--- a/libpurple/protocols/msn/slpmsg.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/slpmsg.c Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "msn.h" #include "slpmsg.h"
--- a/libpurple/protocols/msn/slpmsg.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/slpmsg.h Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _MSN_SLPMSG_H_ #define _MSN_SLPMSG_H_
--- a/libpurple/protocols/msn/slpsession.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/slpsession.c Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "slpsession.h"
--- a/libpurple/protocols/msn/slpsession.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/slpsession.h Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _MSN_SLPSESSION_H_ #define _MSN_SLPSESSION_H_
--- a/libpurple/protocols/msn/state.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/state.c Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "msn.h" #include "state.h"
--- a/libpurple/protocols/msn/state.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/state.h Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _MSN_STATE_H_ #define _MSN_STATE_H_
--- a/libpurple/protocols/msn/switchboard.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/switchboard.c Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "msn.h" #include "prefs.h" @@ -771,7 +771,8 @@ msg->ack_cb(msg, msg->ack_data); swboard = cmdproc->data; - swboard->ack_list = g_list_remove(swboard->ack_list, msg); + if (swboard) + swboard->ack_list = g_list_remove(swboard->ack_list, msg); msn_message_unref(msg); } @@ -951,6 +952,8 @@ PurpleBuddy *buddy; const char *user; + str = NULL; + swboard = cmdproc->data; account = cmdproc->session->account; user = msg->remote_user; @@ -960,9 +963,8 @@ else username = g_markup_escape_text(user, -1); - str = g_strdup_printf(_("%s just sent you a Nudge!"), username); + serv_got_attention(account->gc, buddy->name, MSN_NUDGE); g_free(username); - msn_switchboard_report_user(swboard, PURPLE_MESSAGE_SYSTEM|PURPLE_MESSAGE_NOTIFY, str); g_free(str); }
--- a/libpurple/protocols/msn/switchboard.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/switchboard.h Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _MSN_SWITCHBOARD_H_ #define _MSN_SWITCHBOARD_H_
--- a/libpurple/protocols/msn/sync.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/sync.c Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "msn.h" #include "sync.h"
--- a/libpurple/protocols/msn/sync.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/sync.h Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _MSN_SYNC_H_ #define _MSN_SYNC_H_
--- a/libpurple/protocols/msn/table.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/table.c Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "msn.h" #include "table.h"
--- a/libpurple/protocols/msn/table.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/table.h Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _MSN_TABLE_H_ #define _MSN_TABLE_H_
--- a/libpurple/protocols/msn/transaction.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/transaction.c Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "msn.h" #include "transaction.h"
--- a/libpurple/protocols/msn/transaction.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/transaction.h Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _MSN_TRANSACTION_H #define _MSN_TRANSACTION_H
--- a/libpurple/protocols/msn/user.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/user.c Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "msn.h" #include "user.h"
--- a/libpurple/protocols/msn/user.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/user.h Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _MSN_USER_H_ #define _MSN_USER_H_
--- a/libpurple/protocols/msn/userlist.c Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/userlist.c Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "msn.h" #include "userlist.h"
--- a/libpurple/protocols/msn/userlist.h Sun Aug 19 13:40:34 2007 +0000 +++ b/libpurple/protocols/msn/userlist.h Wed Sep 12 19:11:38 2007 +0000 @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _MSN_USERLIST_H_ #define _MSN_USERLIST_H_
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/myspace/CHANGES Wed Sep 12 19:11:38 2007 +0000 @@ -0,0 +1,156 @@ +2007-08-28 Jeff Connelly <pidgin@xyzzy.cjb.net> - 0.17 +* Get server-side contact list from server on sign-on (partly implements + server-side contacts, ticket #2658). +* Set local alias to username on sign-on, if not already set. This fixes + #2793, though this may not be the best way, other fixes under consideration. +* Support myim:sendIM and addContact URLs with cID and uID parameters. +* Fix #2722, only check for mail if "New mail notifications" is enabled. +* Modularize msimprpl. +* Pidgin 2.1.1 only. + +2007-08-23 Jeff Connelly <pidgin@xyzzy.cjb.net> - 0.16 +* Add option to add all friends from myspace.com to your buddy list (#2660) +* If a user doesn't have a picture, don't display an icon (instead of + displaying MySpace's "no photo" icon) +* Fix #2725, a common crash related to buddy icon data +* Fix #2752, which led to duplicate groups +* Fix #2720, crash/disconnect when adding a buddy that doesn't exist + (You'll now receive an error when looking up invalid usernames). +* Source-code release only. + +2007-08-22 Jeff Connelly <pidgin@xyzzy.cjb.net> - 0.15 +* Incomplete implementation of adding friends from myspace.com. +* Change msim_msg_get() to start at the given node instead of the beginning. +* Add msim_msg_get_*_from_element() to access data in MsimMessagElement *'s. +* Use MsimMessage dictionaries everywhere in incoming messages, instead of + the old GHashTable method. Dictionary type is now fully implemented. +* Add functions to loop over MsimMessages. +* Link to myspace.com profile in Get Info. +* Conditionally use my proposed attention API if defined. +* Propagate to im.pidgin.pidgin branch for 2.1.2. +* GSoC ended on 2007-08-20. The code in this release hasn't changed since + then. I did, however, bump the version number to 0.15 to distinguish this + release from the previous one. But there were no code changes. I updated + the text files, too. +* Note: msimprpl will continue to be developed as time permits. + +2007-08-12 Jeff Connelly <jeff2@soc.pidgin.im> - 0.14 +* Full emoticon support (except no difference between nerd and geek emoticons), + thanks to a number of new icons from Hylke Bons. +* Package Win32 release archive so that it can easily be extracted directly + into the folder Pidgin was installed to. +* Better password handling, may now support Unicode passwords. +* Much general clean-up and restructuring of the code. +* Resolve user ID from buddy list, if it exists. Greatly improves speed of + receiving messages from user IDs. +* Support sending and receiving hyperlinks. +* Fix #2521 by reimplementing protocol message escaping to work correctly. +* Fix #2520 by indicating sign-on at the correct time. + +2007-08-04 Jeff Connelly <jeff2@soc.pidgin.im> - 0.13 +* Fix crash when deleting buddies, on Windows. +* Disable sending client version to oncoming buddies (compile-time option). +* Updated login process (more closely resembles official client). +* Zaps, sending and receiving +* Emoticons, mapped to Pidgin-supported smileys +* Show official client build in buddy profiles. + +2007-07-15 Jeff Connelly <jeff2@soc.pidgin.im> - 0.12 +* Allow logging in with passwords containing uppercase letters (bug #2066) +* Add /3 -> | translation to escaping. +* Allow setting status string. +* Disable keepalive timeout. +* Remove faking self online, instead show real status (now that it exists). +* Support font sizes in incoming instant messages. +* Add support for mail notifications. + +2007-07-09 Jeff Connelly <jeff2@soc.pidgin.im> - 0.11 +* Allow going idle (tested with I'dle Ma'ker) and viewing idle status of + buddies (thanks to Scott Ellis, developing a MySpaceIM plugin for Miranda IM, + for finding the idle status code.) +* Time out if no data from server within a certain amount of time + (keep alives). +* Remove "Sign on as hidden" option, and always set status to current status + when signing on. +* Some support for sending formatted text. +* Fix build process on Unix, bug #2086. + +2007-07-03 Jeff Connelly <jeff2@soc.pidgin.im> - 0.10 +* On incoming instant messages, add support for: + * Text color + * Font face +* Add option to sign on as hidden, default off (previously, always was hidden) +* Add ability to change status to hidden, available, away +* Increase password length limit to 10 to match official client (bug #2010) + +2007-07-01 Jeff Connelly <jeff2@soc.pidgin.im> - 0.9 +* Fix crash on Windows when logging in (bug #1990) +* Fix crash on Windows when viewing tooltip text (bug #1999) + +2007-06-30 Jeff Connelly <jeff2@soc.pidgin.im> - 0.8 +* Allow "Get Info" on all users, by uid or username +* Fix crash when re-logging in, if login failed. +* Show descriptive error message if login password is too long. +* Fake self from being online, since can't add self to buddy list. +* Update for Libpurple 2.0.2. +* Partial support for formatting on incoming instant messages. + +2007-06-14 Jeff Connelly <jeff2@soc.pidgin.im> - 0.7 +* Add/delete buddy now functional (required many other code improvements). +* Show improved buddy information in tooltip text. +* Show user profile (in "Get Info" option) for buddies on buddy list. +* Fix crash when re-logging in, if login succeeded. + +2007-06-12 Jeff Connelly <jeff2@soc.pidgin.im> - 0.6 +* Use RC4 code from Libpurple 2.0.1 +* Use a new implementation for sending and receiving messages (MsimMessage). + This infrastructural change significantly improves extensibility. +* Show online buddies as online. +* Send and receive typing notifications (along with other required changes). + +2007-05-22 Jeff Connelly <jeff2@soc.pidgin.im> - 0.5 +* Add protocol escaping, so can now send and receive / and \ characters +* Designed Pidgin 2.0.0beta7 +* Use RC4 code from Samba +* Use translations (_ macro) +* No major changes to code, still getting familiar with tools & community + +2007-04-29 Jeff Connelly <jeff2@soc.pidgin.im> + +* NOTE: This code is now being developed under Monotone, in the + im.pidgin.soc.2007.msimprpl branch on my local computer, which + is periodically sync'd with pidgin.im's Monotone database. + + Changes will be logged to Monotone. + +2007-04-15 Jeff Connelly <myspaceim@xyzzy.cjb.net> - 0.4 + +* Gracefully handle a full receive buffer +* Handle fatal errors +* Last version for Gaim 2.0.0beta6 + +2007-04-14 Jeff Connelly <myspaceim@xyzzy.cjb.net> - 0.3 + +* Win32 support +* Add a large number of precondition checks and a handful of assertions +* Add documentation to each function, for doxygen. + +2007-04-10 Jeff Connelly <myspaceim@xyzzy.cjb.net> - 0.2 + +* Add ability to IM by email address. +* Show usernames on buddy list instead of userids. +* Show incoming messages as coming from username, instead of userid. +* Add status messages and tooltip text. + +2007-04-09 Jeff Connelly <myspaceim@xyzzy.cjb.net> - 0.1 + +* Parsing most of the protocol. +* Logging in using RC4/SHA1-based authentication. +* Sending messages, by numeric userid or username. +* Receiving messages, currently only by numeric userid. +* Some buddy list support (show all users on buddy list as online, by uid). + +2007-04-07 Jeff Connelly <myspaceim@xyzzy.cjb.net> - 0.0 + +* Initial version. Login only. Not publicly released. +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/myspace/ChangeLog Wed Sep 12 19:11:38 2007 +0000 @@ -0,0 +1,39 @@ + +2007-04-29 Jeff Connelly <jeff2@soc.pidgin.com> + +* NOTE: This code is now being developed under Monotone, in the + im.pidgin.soc.2007.msimprpl branch on my local computer, which + is periodically sync'd with pidgin.im's Monotone database. + + Changes will be logged to Monotone. + +2007-04-15 Jeff Connelly <myspaceim@xyzzy.cjb.net> - 0.4 + +* Gracefully handle a full receive buffer +* Handle fatal errors + +2007-04-14 Jeff Connelly <myspaceim@xyzzy.cjb.net> - 0.3 + +* Win32 support +* Add a large number of precondition checks and a handful of assertions +* Add documentation to each function, for doxygen. + +2007-04-10 Jeff Connelly <myspaceim@xyzzy.cjb.net> - 0.2 + +* Add ability to IM by email address. +* Show usernames on buddy list instead of userids. +* Show incoming messages as coming from username, instead of userid. +* Add status messages and tooltip text. + +2007-04-09 Jeff Connelly <myspaceim@xyzzy.cjb.net> - 0.1 + +* Parsing most of the protocol. +* Logging in using RC4/SHA1-based authentication. +* Sending messages, by numeric userid or username. +* Receiving messages, currently only by numeric userid. +* Some buddy list support (show all users on buddy list as online, by uid). + +2007-04-07 Jeff Connelly <myspaceim@xyzzy.cjb.net> - 0.0 + +* Initial version. Login only. Not publicly released. +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/myspace/LICENSE Wed Sep 12 19:11:38 2007 +0000 @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + 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 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/myspace/Makefile.am Wed Sep 12 19:11:38 2007 +0000 @@ -0,0 +1,41 @@ +EXTRA_DIST = Makefile.mingw + +pkgdir = $(libdir)/purple-$(PURPLE_MAJOR_VERSION) + +SOURCES = myspace.c \ + myspace.h \ + persist.h \ + message.c \ + message.h \ + zap.c \ + session.c \ + session.h \ + markup.c \ + markup.h \ + user.c \ + user.h + +AM_CFLAGS = $(st) + +libmyspace_la_LDFLAGS = -module -avoid-version + +if STATIC_MYSPACE + +st = -DPURPLE_STATIC_PRPL +noinst_LIBRARIES = libmyspace.a +libmyspace_a_SOURCES = $(SOURCES) +libmyspace_a_CFLAGS = $(AM_CFLAGS) + +else + +st = +pkg_LTLIBRARIES = libmyspace.la +libmyspace_la_SOURCES = $(SOURCES) +libmyspace_la_LIBADD = $(GLIB_LIBS) + +endif + +AM_CPPFLAGS = \ + -I$(top_srcdir)/libpurple \ + $(GLIB_CFLAGS) \ + $(DEBUG_CFLAGS)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/myspace/Makefile.mingw Wed Sep 12 19:11:38 2007 +0000 @@ -0,0 +1,81 @@ +# +# Makefile.mingw +# +# Description: Makefile for win32 (mingw) version of libmyspace +# + +PIDGIN_TREE_TOP := ../../.. +include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak + +TARGET = libmyspace +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 = myspace.c message.c zap.c session.c markup.c user.c + +OBJECTS = $(C_SRC:%.c=%.o) + +## +## LIBRARIES +## +LIBS = \ + -lglib-2.0 \ + -lws2_32 \ + -lintl \ + -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) + +## +## BUILD DLL +## +$(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/myspace/README Wed Sep 12 19:11:38 2007 +0000 @@ -0,0 +1,32 @@ +MySpaceIM Protocol Plugin for Libpurple by Jeff Connelly 20070807 + + +Greetings. This package contains a plugin for libpurple (as used in +Pidgin, formerly Gaim) to connect to the new MySpaceIM instant messaging +network and send/receive messages. Functionality is only basic as of yet, +and this code should be considered alpha quality. + +This code was initially developed under Google Summer of Code 2007. + +For features and TODO, see http://developer.pidgin.im/wiki/MySpaceIM + +Windows installation: Unzip the archive to C:\Program Files\Pidgin +Unix/source installation: run "make install" + +Usage: + +Login using your _email address_ you use to login to myspace.com. You can't +login using your numeric ID or alias. + +To test it out, send a message to yourself (by your username or numeric +uid (email not yet supported)) or tom (6221). In either case you should +get a reply. You should also be able to talk to other MySpaceIM users if +you desire. Replies will always be shown as coming from a user's username, +even if you IM by email or userid. + +Feedback welcome. You can IM my test account at "msimprpl" if you feel like it. + +Enjoy, +-Jeff Connelly +msimprpl@xyzzy.cjb.net +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/myspace/markup.c Wed Sep 12 19:11:38 2007 +0000 @@ -0,0 +1,691 @@ +/* MySpaceIM Protocol Plugin - markup + * + * Copyright (C) 2007, Jeff Connelly <jeff2@soc.pidgin.im> + * + * 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 "myspace.h" + +typedef void (*MSIM_XMLNODE_CONVERT)(MsimSession *, xmlnode *, gchar **, gchar **); + +/* Internal functions */ + +static guint msim_point_to_purple_size(MsimSession *session, guint point); +static guint msim_purple_size_to_point(MsimSession *session, guint size); +static guint msim_height_to_point(MsimSession *session, guint height); +static guint msim_point_to_height(MsimSession *session, guint point); + +static void msim_markup_tag_to_html(MsimSession *, xmlnode *root, gchar **begin, gchar **end); +static void html_tag_to_msim_markup(MsimSession *, xmlnode *root, gchar **begin, gchar **end); +static gchar *msim_convert_xml(MsimSession *, const gchar *raw, MSIM_XMLNODE_CONVERT f); +static gchar *msim_convert_smileys_to_markup(gchar *before); +static double msim_round(double round); + + +/* Globals */ + +/* The names in in emoticon_names (for <i n=whatever>) map to corresponding + * entries in emoticon_symbols (for the ASCII representation of the emoticon). + * + * Multiple emoticon symbols in Pidgin can map to one name. List the + * canonical form, as inserted by the "Smile!" dialog, first. For example, + * :) comes before :-), because although both are recognized as 'happy', + * the first is inserted by the smiley button (first symbol in theme). + * + * Note that symbols are case-sensitive in Pidgin -- :-X is not :-x. */ +static struct MSIM_EMOTICON +{ + gchar *name; + gchar *symbol; +} msim_emoticons[] = { + /* Unfortunately, this list duplicates much of the file + * pidgin/pidgin/pixmaps/emotes/default/22/default.theme.in, because + * that file is part of Pidgin, but we're part of libpurple. + */ + { "bigsmile", ":D" }, + { "bigsmile", ":-D" }, + { "devil", "}:)" }, + { "frazzled", ":Z" }, + { "geek", "B)" }, + { "googles", "%)" }, + { "growl", ":E" }, + { "laugh", ":))" }, /* Must be before ':)' */ + { "happy", ":)" }, + { "happy", ":-)" }, + { "happi", ":)" }, + { "heart", ":X" }, + { "mohawk", "-:" }, + { "mad", "X(" }, + { "messed", "X)" }, + { "nerd", "Q)" }, + { "oops", ":G" }, + { "pirate", "P)" }, + { "scared", ":O" }, + { "sidefrown", ":{" }, + { "sinister", ":B" }, + { "smirk", ":," }, + { "straight", ":|" }, + { "tongue", ":P" }, + { "tongue", ":p" }, + { "tongy", ":P" }, + { "upset", "B|" }, + { "wink", ";-)" }, + { "wink", ";)" }, + { "winc", ";)" }, + { "worried", ":[" }, + { "kiss", ":x" }, + { NULL, NULL } +}; + + + +/* Indexes of this array + 1 map HTML font size to scale of normal font size. * + * Based on _point_sizes from libpurple/gtkimhtml.c + * 1 2 3 4 5 6 7 */ +static gdouble _font_scale[] = { .85, .95, 1, 1.2, 1.44, 1.728, 2.0736 }; + +#define MAX_FONT_SIZE 7 /* Purple maximum font size */ +#define POINTS_PER_INCH 72 /* How many pt's in an inch */ + +/* Text formatting bits for <f s=#> */ +#define MSIM_TEXT_BOLD 1 +#define MSIM_TEXT_ITALIC 2 +#define MSIM_TEXT_UNDERLINE 4 + +/* Default baseline size of purple's fonts, in points. What is size 3 in points. + * _font_scale specifies scaling factor relative to this point size. Note this + * is only the default; it is configurable in account options. */ +#define MSIM_BASE_FONT_POINT_SIZE 8 + +/* Default display's DPI. 96 is common but it can differ. Also configurable + * in account options. */ +#define MSIM_DEFAULT_DPI 96 + + +/* round is part of C99, but sometimes is unavailable before then. + * Based on http://forums.belution.com/en/cpp/000/050/13.shtml + */ +double msim_round(double value) +{ + if (value < 0) { + return -(floor(-value + 0.5)); + } else { + return floor( value + 0.5); + } +} + + +/** Convert typographical font point size to HTML font size. + * Based on libpurple/gtkimhtml.c */ +static guint +msim_point_to_purple_size(MsimSession *session, guint point) +{ + guint size, this_point, base; + gdouble scale; + + base = purple_account_get_int(session->account, "base_font_size", MSIM_BASE_FONT_POINT_SIZE); + + for (size = 0; + size < sizeof(_font_scale) / sizeof(_font_scale[0]); + ++size) { + scale = _font_scale[CLAMP(size, 1, MAX_FONT_SIZE) - 1]; + this_point = (guint)msim_round(scale * base); + + if (this_point >= point) { + purple_debug_info("msim", "msim_point_to_purple_size: %d pt -> size=%d\n", + point, size); + return size; + } + } + + /* No HTML font size was this big; return largest possible. */ + return this_point; +} + +/** Convert HTML font size to point size. */ +static guint +msim_purple_size_to_point(MsimSession *session, guint size) +{ + gdouble scale; + guint point; + guint base; + + scale = _font_scale[CLAMP(size, 1, MAX_FONT_SIZE) - 1]; + + base = purple_account_get_int(session->account, "base_font_size", MSIM_BASE_FONT_POINT_SIZE); + + point = (guint)msim_round(scale * base); + + purple_debug_info("msim", "msim_purple_size_to_point: size=%d -> %d pt\n", + size, point); + + return point; +} + +/** Convert a msim markup font pixel height to the more usual point size, for incoming messages. */ +static guint +msim_height_to_point(MsimSession *session, guint height) +{ + guint dpi; + + dpi = purple_account_get_int(session->account, "port", MSIM_DEFAULT_DPI); + + return (guint)msim_round((POINTS_PER_INCH * 1. / dpi) * height); + + /* See also: libpurple/protocols/bonjour/jabber.c + * _font_size_ichat_to_purple */ +} + +/** Convert point size to msim pixel height font size specification, for outgoing messages. */ +static guint +msim_point_to_height(MsimSession *session, guint point) +{ + guint dpi; + + dpi = purple_account_get_int(session->account, "port", MSIM_DEFAULT_DPI); + + return (guint)msim_round((dpi * 1. / POINTS_PER_INCH) * point); +} + +/** Convert the msim markup <f> (font) tag into HTML. */ +static void +msim_markup_f_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) +{ + const gchar *face, *height_str, *decor_str; + GString *gs_end, *gs_begin; + guint decor, height; + + face = xmlnode_get_attrib(root, "f"); + height_str = xmlnode_get_attrib(root, "h"); + decor_str = xmlnode_get_attrib(root, "s"); + + if (height_str) { + height = atol(height_str); + } else { + height = 12; + } + + if (decor_str) { + decor = atol(decor_str); + } else { + decor = 0; + } + + gs_begin = g_string_new(""); + /* TODO: get font size working */ + if (height && !face) { + g_string_printf(gs_begin, "<font size='%d'>", + msim_point_to_purple_size(session, msim_height_to_point(session, height))); + } else if (height && face) { + g_string_printf(gs_begin, "<font face='%s' size='%d'>", face, + msim_point_to_purple_size(session, msim_height_to_point(session, height))); + } else { + g_string_printf(gs_begin, "<font>"); + } + + /* No support for font-size CSS? */ + /* g_string_printf(gs_begin, "<span style='font-family: %s; font-size: %dpt'>", face, + msim_height_to_point(height)); */ + + gs_end = g_string_new("</font>"); + + if (decor & MSIM_TEXT_BOLD) { + g_string_append(gs_begin, "<b>"); + g_string_prepend(gs_end, "</b>"); + } + + if (decor & MSIM_TEXT_ITALIC) { + g_string_append(gs_begin, "<i>"); + g_string_append(gs_end, "</i>"); + } + + if (decor & MSIM_TEXT_UNDERLINE) { + g_string_append(gs_begin, "<u>"); + g_string_append(gs_end, "</u>"); + } + + + *begin = gs_begin->str; + *end = gs_end->str; +} + +/** Convert a msim markup color to a color suitable for libpurple. + * + * @param msim Either a color name, or an rgb(x,y,z) code. + * + * @return A new string, either a color name or #rrggbb code. Must g_free(). + */ +static char * +msim_color_to_purple(const char *msim) +{ + guint red, green, blue; + + if (!msim) { + return g_strdup("black"); + } + + if (sscanf(msim, "rgb(%d,%d,%d)", &red, &green, &blue) != 3) { + /* Color name. */ + return g_strdup(msim); + } + /* TODO: rgba (alpha). */ + + return g_strdup_printf("#%.2x%.2x%.2x", red, green, blue); +} + +/** Convert the msim markup <a> (anchor) tag into HTML. */ +static void +msim_markup_a_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) +{ + const gchar *href; + + href = xmlnode_get_attrib(root, "h"); + if (!href) { + href = ""; + } + + *begin = g_strdup_printf("<a href=\"%s\">%s", href, href); + *end = g_strdup("</a>"); +} + +/** Convert the msim markup <p> (paragraph) tag into HTML. */ +static void +msim_markup_p_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) +{ + /* Just pass through unchanged. + * + * Note: attributes currently aren't passed, if there are any. */ + *begin = g_strdup("<p>"); + *end = g_strdup("</p>"); +} + +/** Convert the msim markup <c> tag (text color) into HTML. TODO: Test */ +static void +msim_markup_c_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) +{ + const gchar *color; + gchar *purple_color; + + color = xmlnode_get_attrib(root, "v"); + if (!color) { + purple_debug_info("msim", "msim_markup_c_to_html: <c> tag w/o v attr\n"); + *begin = g_strdup(""); + *end = g_strdup(""); + /* TODO: log as unrecognized */ + return; + } + + purple_color = msim_color_to_purple(color); + + *begin = g_strdup_printf("<font color='%s'>", purple_color); + + g_free(purple_color); + + /* *begin = g_strdup_printf("<span style='color: %s'>", color); */ + *end = g_strdup("</font>"); +} + +/** Convert the msim markup <b> tag (background color) into HTML. TODO: Test */ +static void +msim_markup_b_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) +{ + const gchar *color; + gchar *purple_color; + + color = xmlnode_get_attrib(root, "v"); + if (!color) { + *begin = g_strdup(""); + *end = g_strdup(""); + purple_debug_info("msim", "msim_markup_b_to_html: <b> w/o v attr\n"); + /* TODO: log as unrecognized. */ + return; + } + + purple_color = msim_color_to_purple(color); + + /* TODO: find out how to set background color. */ + *begin = g_strdup_printf("<span style='background-color: %s'>", + purple_color); + g_free(purple_color); + + *end = g_strdup("</p>"); +} + +/** Convert the msim markup <i> tag (emoticon image) into HTML. */ +static void +msim_markup_i_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) +{ + const gchar *name; + guint i; + struct MSIM_EMOTICON *emote; + + name = xmlnode_get_attrib(root, "n"); + if (!name) { + purple_debug_info("msim", "msim_markup_i_to_html: <i> w/o n\n"); + *begin = g_strdup(""); + *end = g_strdup(""); + /* TODO: log as unrecognized */ + return; + } + + /* Find and use canonical form of smiley symbol. */ + for (i = 0; (emote = &msim_emoticons[i]) && emote->name != NULL; ++i) { + if (g_str_equal(name, emote->name)) { + *begin = g_strdup(emote->symbol); + *end = g_strdup(""); + return; + } + } + + /* Couldn't find it, sorry. Try to degrade gracefully. */ + *begin = g_strdup_printf("**%s**", name); + *end = g_strdup(""); +} + +/** Convert an individual msim markup tag to HTML. */ +static void +msim_markup_tag_to_html(MsimSession *session, xmlnode *root, gchar **begin, + gchar **end) +{ + if (g_str_equal(root->name, "f")) { + msim_markup_f_to_html(session, root, begin, end); + } else if (g_str_equal(root->name, "a")) { + msim_markup_a_to_html(session, root, begin, end); + } else if (g_str_equal(root->name, "p")) { + msim_markup_p_to_html(session, root, begin, end); + } else if (g_str_equal(root->name, "c")) { + msim_markup_c_to_html(session, root, begin, end); + } else if (g_str_equal(root->name, "b")) { + msim_markup_b_to_html(session, root, begin, end); + } else if (g_str_equal(root->name, "i")) { + msim_markup_i_to_html(session, root, begin, end); + } else { + purple_debug_info("msim", "msim_markup_tag_to_html: " + "unknown tag name=%s, ignoring", + (root && root->name) ? root->name : "(NULL)"); + *begin = g_strdup(""); + *end = g_strdup(""); + } +} + +/** Convert an individual HTML tag to msim markup. */ +static void +html_tag_to_msim_markup(MsimSession *session, xmlnode *root, gchar **begin, + gchar **end) +{ + /* TODO: Coalesce nested tags into one <f> tag! + * Currently, the 's' value will be overwritten when b/i/u is nested + * within another one, and only the inner-most formatting will be + * applied to the text. */ + if (!purple_utf8_strcasecmp(root->name, "root")) { + *begin = g_strdup(""); + *end = g_strdup(""); + } else if (!purple_utf8_strcasecmp(root->name, "b")) { + *begin = g_strdup_printf("<f s='%d'>", MSIM_TEXT_BOLD); + *end = g_strdup("</f>"); + } else if (!purple_utf8_strcasecmp(root->name, "i")) { + *begin = g_strdup_printf("<f s='%d'>", MSIM_TEXT_ITALIC); + *end = g_strdup("</f>"); + } else if (!purple_utf8_strcasecmp(root->name, "u")) { + *begin = g_strdup_printf("<f s='%d'>", MSIM_TEXT_UNDERLINE); + *end = g_strdup("</f>"); + } else if (!purple_utf8_strcasecmp(root->name, "a")) { + const gchar *href, *link_text; + + href = xmlnode_get_attrib(root, "href"); + + if (!href) { + href = xmlnode_get_attrib(root, "HREF"); + } + + link_text = xmlnode_get_data(root); + + if (href) { + if (g_str_equal(link_text, href)) { + /* Purple gives us: <a href="URL">URL</a> + * Translate to <a h='URL' /> + * Displayed as text of URL with link to URL + */ + *begin = g_strdup_printf("<a h='%s' />", href); + } else { + /* But if we get: <a href="URL">text</a> + * Translate to: text: <a h='URL' /> + * + * Because official client only supports self-closed <a> + * tags; you can't change the link text. + */ + *begin = g_strdup_printf("%s: <a h='%s' />", link_text, href); + } + } else { + *begin = g_strdup("<a />"); + } + + /* Sorry, kid. MySpace doesn't support you within <a> tags. */ + xmlnode_free(root->child); + root->child = NULL; + + *end = g_strdup(""); + } else if (!purple_utf8_strcasecmp(root->name, "font")) { + const gchar *size; + const gchar *face; + + size = xmlnode_get_attrib(root, "size"); + face = xmlnode_get_attrib(root, "face"); + + if (face && size) { + *begin = g_strdup_printf("<f f='%s' h='%d'>", face, + msim_point_to_height(session, + msim_purple_size_to_point(session, atoi(size)))); + } else if (face) { + *begin = g_strdup_printf("<f f='%s'>", face); + } else if (size) { + *begin = g_strdup_printf("<f h='%d'>", + msim_point_to_height(session, + msim_purple_size_to_point(session, atoi(size)))); + } else { + *begin = g_strdup("<f>"); + } + + *end = g_strdup("</f>"); + + /* TODO: color (bg uses <body>), emoticons */ + } else { + *begin = g_strdup_printf("[%s]", root->name); + *end = g_strdup_printf("[/%s]", root->name); + } +} + +/** Convert an xmlnode of msim markup or HTML to an HTML string or msim markup. + * + * @param f Function to convert tags. + * + * @return An HTML string. Caller frees. + */ +static gchar * +msim_convert_xmlnode(MsimSession *session, xmlnode *root, MSIM_XMLNODE_CONVERT f) +{ + xmlnode *node; + gchar *begin, *inner, *end; + GString *final; + + if (!root || !root->name) { + return g_strdup(""); + } + + purple_debug_info("msim", "msim_convert_xmlnode: got root=%s\n", + root->name); + + begin = inner = end = NULL; + + final = g_string_new(""); + + f(session, root, &begin, &end); + + g_string_append(final, begin); + + /* Loop over all child nodes. */ + for (node = root->child; node != NULL; node = node->next) { + switch (node->type) { + case XMLNODE_TYPE_ATTRIB: + /* Attributes handled above. */ + break; + + case XMLNODE_TYPE_TAG: + /* A tag or tag with attributes. Recursively descend. */ + inner = msim_convert_xmlnode(session, node, f); + g_return_val_if_fail(inner != NULL, NULL); + + purple_debug_info("msim", " ** node name=%s\n", + (node && node->name) ? node->name : "(NULL)"); + break; + + case XMLNODE_TYPE_DATA: + /* Literal text. */ + inner = g_new0(char, node->data_sz + 1); + strncpy(inner, node->data, node->data_sz); + inner[node->data_sz] = 0; + + purple_debug_info("msim", " ** node data=%s\n", + inner ? inner : "(NULL)"); + break; + + default: + purple_debug_info("msim", + "msim_convert_xmlnode: strange node\n"); + inner = g_strdup(""); + } + + if (inner) { + g_string_append(final, inner); + } + } + + /* TODO: Note that msim counts each piece of text enclosed by <f> as + * a paragraph and will display each on its own line. You actually have + * to _nest_ <f> tags to intersperse different text in one paragraph! + * Comment out this line below to see. */ + g_string_append(final, end); + + purple_debug_info("msim", "msim_markup_xmlnode_to_gtkhtml: RETURNING %s\n", + (final && final->str) ? final->str : "(NULL)"); + + return final->str; +} + +/** Convert XML to something based on MSIM_XMLNODE_CONVERT. */ +static gchar * +msim_convert_xml(MsimSession *session, const gchar *raw, MSIM_XMLNODE_CONVERT f) +{ + xmlnode *root; + gchar *str; + gchar *enclosed_raw; + + g_return_val_if_fail(raw != NULL, NULL); + + /* Enclose text in one root tag, to try to make it valid XML for parsing. */ + enclosed_raw = g_strconcat("<root>", raw, "</root>", NULL); + + root = xmlnode_from_str(enclosed_raw, -1); + + if (!root) { + purple_debug_info("msim", "msim_markup_to_html: couldn't parse " + "%s as XML, returning raw: %s\n", enclosed_raw, raw); + /* TODO: msim_unrecognized */ + g_free(enclosed_raw); + return g_strdup(raw); + } + + g_free(enclosed_raw); + + str = msim_convert_xmlnode(session, root, f); + g_return_val_if_fail(str != NULL, NULL); + purple_debug_info("msim", "msim_markup_to_html: returning %s\n", str); + + xmlnode_free(root); + + return str; +} + +/** Convert plaintext smileys to <i> markup tags. + * + * @param before Original text with ASCII smileys. Will be freed. + * @return A new string with <i> tags, if applicable. Must be g_free()'d. + */ +static gchar * +msim_convert_smileys_to_markup(gchar *before) +{ + gchar *old, *new, *replacement; + guint i; + struct MSIM_EMOTICON *emote; + + old = before; + new = NULL; + + for (i = 0; (emote = &msim_emoticons[i]) && emote->name != NULL; ++i) { + gchar *name, *symbol; + + name = emote->name; + symbol = emote->symbol; + + replacement = g_strdup_printf("<i n=\"%s\"/>", name); + + purple_debug_info("msim", "msim_convert_smileys_to_markup: %s->%s\n", + symbol ? symbol : "(NULL)", + replacement ? replacement : "(NULL)"); + new = purple_strreplace(old, symbol, replacement); + + g_free(replacement); + g_free(old); + + old = new; + } + + return new; +} + + +/** High-level function to convert MySpaceIM markup to Purple (HTML) markup. + * + * @return Purple markup string, must be g_free()'d. */ +gchar * +msim_markup_to_html(MsimSession *session, const gchar *raw) +{ + return msim_convert_xml(session, raw, + (MSIM_XMLNODE_CONVERT)(msim_markup_tag_to_html)); +} + +/** High-level function to convert Purple (HTML) to MySpaceIM markup. + * + * TODO: consider using purple_markup_html_to_xhtml() to make valid XML. + * + * @return HTML markup string, must be g_free()'d. */ +gchar * +html_to_msim_markup(MsimSession *session, const gchar *raw) +{ + gchar *markup; + + markup = msim_convert_xml(session, raw, + (MSIM_XMLNODE_CONVERT)(html_tag_to_msim_markup)); + + if (purple_account_get_bool(session->account, "emoticons", TRUE)) { + /* Frees markup and allocates a new one. */ + markup = msim_convert_smileys_to_markup(markup); + } + + return markup; +} + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/myspace/markup.h Wed Sep 12 19:11:38 2007 +0000 @@ -0,0 +1,27 @@ +/* MySpaceIM Protocol Plugin - markup + * + * Copyright (C) 2007, Jeff Connelly <jeff2@soc.pidgin.im> + * + * 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 _MYSPACE_MARKUP_H +#define _MYSPACE_MARKUP_H + +/* High-level msim markup <=> Purple html conversion functions. */ +gchar *msim_markup_to_html(MsimSession *, const gchar *raw); +gchar *html_to_msim_markup(MsimSession *, const gchar *raw); + +#endif /* !_MYSPACE_MARKUP_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/myspace/message.c Wed Sep 12 19:11:38 2007 +0000 @@ -0,0 +1,1358 @@ +/** MySpaceIM protocol messages + * + * \author Jeff Connelly + * + * Copyright (C) 2007, Jeff Connelly <jeff2@soc.pidgin.im> + * + * 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 "myspace.h" +#include "message.h" + +static void msim_msg_free_element(gpointer data, gpointer user_data); +static void msim_msg_debug_string_element(gpointer data, gpointer user_data); +static gchar *msim_msg_pack_using(MsimMessage *msg, GFunc gf, const gchar *sep, const gchar *begin, const gchar *end); +static GList *msim_msg_get_node(MsimMessage *msg, const gchar *name); +static MsimMessage *msim_msg_new_v(gchar *first_key, va_list argp); + +/* Escape codes and associated replacement text, used for protocol message + * escaping and unescaping. */ +static struct MSIM_ESCAPE_REPLACEMENT { + gchar *code; + gchar text; +} msim_escape_replacements[] = { + { "/1", '/' }, + { "/2", '\\' }, + /* { "/3", "|" }, */ /* Not used here -- only for within arrays */ + { NULL, 0 } +}; + +/** + * Escape a protocol message. + * + * @return The escaped message. Caller must g_free(). + */ +gchar * +msim_escape(const gchar *msg) +{ + GString *gs; + guint i, j; + + gs = g_string_new(""); + + + for (i = 0; i < strlen(msg); ++i) { + struct MSIM_ESCAPE_REPLACEMENT *replacement; + gchar *replace; + + replace = NULL; + + /* Check for characters that need to be escaped, and escape them. */ + for (j = 0; (replacement = &msim_escape_replacements[j]) && + replacement->code != NULL; ++j) { + if (msg[i] == replacement->text) { + replace = replacement->code; + break; + } + } + + if (replace) { + g_string_append(gs, replace); + } else { + g_string_append_c(gs, msg[i]); + } + } + +#ifdef MSIM_DEBUG_ESCAPE + purple_debug_info("msim", "msim_escape: msg=%s, ret=%s\n", msg, gs->str); +#endif + + return gs->str; +} + +/** + * Unescape a protocol message. + * + * @return The unescaped message, caller must g_free(). + */ +gchar * +msim_unescape(const gchar *msg) +{ + GString *gs; + guint i, j; + + gs = g_string_new(""); + + for (i = 0; i < strlen(msg); ++i) { + struct MSIM_ESCAPE_REPLACEMENT *replacement; + gchar replace; + + replace = msg[i]; + + for (j = 0; (replacement = &msim_escape_replacements[j]) && + replacement->code != NULL; ++j) { + if (msg[i] == replacement->code[0] && + i + 1 < strlen(msg) && + msg[i + 1] == replacement->code[1]) { + replace = replacement->text; + ++i; + break; + } + } + + g_string_append_c(gs, replace); + } + +#ifdef MSIM_DEBUG_ESCAPE + purple_debug_info("msim", "msim_unescape: msg=%s, ret=%s\n", msg, gs->str); +#endif + + return gs->str; +} + +/** Create a new MsimMessage. + * + * @param first_key The first key in the sequence, or NULL for an empty message. + * @param ... A sequence of gchar* key/type/value triplets, terminated with NULL. + * + * See msim_msg_append() documentation for details on types. + */ +MsimMessage * +msim_msg_new(gchar *first_key, ...) +{ + va_list argp; + + if (first_key) { + va_start(argp, first_key); + return msim_msg_new_v(first_key, argp); + } else { + return NULL; + } +} + +/** Create a new message from va_list and its first argument. + * + * @param first_key The first argument (a key), or NULL to take all arguments + * from argp. + * @param argp A va_list of variadic arguments, already started with va_start(). Will be va_end()'d. + * @return New MsimMessage *, must be freed with msim_msg_free(). + * + * For internal use - users probably want msim_msg_new() or msim_send(). + */ +static MsimMessage * +msim_msg_new_v(gchar *first_key, va_list argp) +{ + gchar *key, *value; + MsimMessageType type; + MsimMessage *msg; + gboolean first; + + GString *gs; + GList *gl; + MsimMessage *dict; + + /* Begin with an empty message. */ + msg = NULL; + + /* First parameter can be given explicitly. */ + first = first_key != NULL; + + /* Read key, type, value triplets until NULL. */ + do { + if (first) { + key = first_key; + first = FALSE; + } else { + key = va_arg(argp, gchar *); + if (!key) { + break; + } + } + + type = va_arg(argp, int); + + /* Interpret variadic arguments. */ + switch (type) { + case MSIM_TYPE_INTEGER: + case MSIM_TYPE_BOOLEAN: + msg = msim_msg_append(msg, key, type, GUINT_TO_POINTER(va_arg(argp, int))); + break; + + case MSIM_TYPE_STRING: + value = va_arg(argp, char *); + + g_return_val_if_fail(value != NULL, FALSE); + + msg = msim_msg_append(msg, key, type, value); + break; + + case MSIM_TYPE_BINARY: + gs = va_arg(argp, GString *); + + g_return_val_if_fail(gs != NULL, FALSE); + + /* msim_msg_free() will free this GString the caller created. */ + msg = msim_msg_append(msg, key, type, gs); + break; + + case MSIM_TYPE_LIST: + gl = va_arg(argp, GList *); + + g_return_val_if_fail(gl != NULL, FALSE); + + msg = msim_msg_append(msg, key, type, gl); + break; + + case MSIM_TYPE_DICTIONARY: + dict = va_arg(argp, MsimMessage *); + + g_return_val_if_fail(dict != NULL, FALSE); + + msg = msim_msg_append(msg, key, type, dict); + break; + + default: + purple_debug_info("msim", "msim_send: unknown type %d\n", type); + break; + } + } while(key); + va_end(argp); + + return msg; +} + +/** Perform a deep copy on a GList * of gchar * strings. Free with msim_msg_list_free(). */ +GList * +msim_msg_list_copy(GList *old) +{ + GList *new_list; + + new_list = NULL; + + /* Deep copy (g_list_copy is shallow). Copy each string. */ + for (; old != NULL; old = g_list_next(old)) { + new_list = g_list_append(new_list, g_strdup(old->data)); + } + + return new_list; +} + +/** Free a GList * of MsimMessageElement *'s. */ +void +msim_msg_list_free(GList *l) +{ + + for (; l != NULL; l = g_list_next(l)) { + MsimMessageElement *elem; + + elem = (MsimMessageElement *)l->data; + + /* Note that name is almost never dynamically allocated elsewhere; + * it is usually a static string, but not in lists. So cast it. */ + g_free((gchar *)elem->name); + g_free(elem->data); + g_free(elem); + } + g_list_free(l); +} + +/** Parse a |-separated string into a new GList. Free with msim_msg_list_free(). */ +GList * +msim_msg_list_parse(const gchar *raw) +{ + gchar **array; + GList *list; + guint i; + + array = g_strsplit(raw, "|", 0); + list = NULL; + + /* TODO: escape/unescape /3 <-> | within list elements */ + + for (i = 0; array[i] != NULL; ++i) { + MsimMessageElement *elem; + + /* Freed in msim_msg_list_free() */ + elem = g_new0(MsimMessageElement, 1); + + /* Give the element a name for debugging purposes. + * Not supposed to be looked up by this name; instead, + * lookup the elements by indexing the array. */ + elem->name = g_strdup_printf("(list item #%d)", i); + elem->type = MSIM_TYPE_RAW; + elem->data = g_strdup(array[i]); + + list = g_list_append(list, elem); + } + + g_strfreev(array); + + return list; +} + +/** Clone an individual element. + * + * @param data MsimMessageElement * to clone. + * @param user_data Pointer to MsimMessage * to add cloned element to. + */ +static void +msim_msg_clone_element(gpointer data, gpointer user_data) +{ + MsimMessageElement *elem; + MsimMessage **new; + gpointer new_data; + + GString *gs; + MsimMessage *dict; + + elem = (MsimMessageElement *)data; + new = (MsimMessage **)user_data; + + switch (elem->type) { + case MSIM_TYPE_BOOLEAN: + case MSIM_TYPE_INTEGER: + new_data = elem->data; + break; + + case MSIM_TYPE_RAW: + case MSIM_TYPE_STRING: + new_data = g_strdup((gchar *)elem->data); + break; + + case MSIM_TYPE_LIST: + new_data = (gpointer)msim_msg_list_copy((GList *)(elem->data)); + break; + + case MSIM_TYPE_BINARY: + gs = (GString *)elem->data; + + new_data = g_string_new_len(gs->str, gs->len); + break; + case MSIM_TYPE_DICTIONARY: + dict = (MsimMessage *)elem->data; + + new_data = msim_msg_clone(dict); + break; + + default: + purple_debug_info("msim", "msim_msg_clone_element: unknown type %d\n", elem->type); + g_return_if_fail(NULL); + } + + /* Append cloned data. Note that the 'name' field is a static string, so it + * never needs to be copied nor freed. */ + *new = msim_msg_append(*new, elem->name, elem->type, new_data); +} + +/** Clone an existing MsimMessage. + * + * @return Cloned message; caller should free with msim_msg_free(). + */ +MsimMessage * +msim_msg_clone(MsimMessage *old) +{ + MsimMessage *new; + + if (old == NULL) { + return NULL; + } + + new = msim_msg_new(FALSE); + + g_list_foreach(old, msim_msg_clone_element, &new); + + return new; +} + +/** Free the data of a message element. + * + * @param elem The MsimMessageElement * + * + * Note this only frees the element data; you may also want to free the + * element itself with g_free() (see msim_msg_free_element()). + */ +void +msim_msg_free_element_data(MsimMessageElement *elem) +{ + switch (elem->type) { + case MSIM_TYPE_BOOLEAN: + case MSIM_TYPE_INTEGER: + /* Integer value stored in gpointer - no need to free(). */ + break; + + case MSIM_TYPE_RAW: + case MSIM_TYPE_STRING: + /* Always free strings - caller should have g_strdup()'d if + * string was static or temporary and not to be freed. */ + g_free(elem->data); + break; + + case MSIM_TYPE_BINARY: + /* Free the GString itself and the binary data. */ + g_string_free((GString *)elem->data, TRUE); + break; + + case MSIM_TYPE_DICTIONARY: + msim_msg_free((MsimMessage *)elem->data); + break; + + case MSIM_TYPE_LIST: + g_list_free((GList *)elem->data); + break; + + default: + purple_debug_info("msim", "msim_msg_free_element_data: " + "not freeing unknown type %d\n", elem->type); + break; + } +} + +/** Free an individual message element. + * + * @param data MsimMessageElement * to free. + * @param user_data Not used; required to match g_list_foreach() callback prototype. + * + * Frees both the element data and the element itself. + */ +static void +msim_msg_free_element(gpointer data, gpointer user_data) +{ + MsimMessageElement *elem; + + elem = (MsimMessageElement *)data; + + msim_msg_free_element_data(elem); + + g_free(elem); +} + +/** Free a complete message. */ +void +msim_msg_free(MsimMessage *msg) +{ + if (!msg) { + /* already free as can be */ + return; + } + +#ifdef MSIM_MSG_DEBUG_FREE + msim_msg_dump("msim_msg_free: freeing %s", msg); +#endif + + g_list_foreach(msg, msim_msg_free_element, NULL); + g_list_free(msg); +} + +/** Send an existing MsimMessage. */ +gboolean +msim_msg_send(MsimSession *session, MsimMessage *msg) +{ + gchar *raw; + gboolean success; + + raw = msim_msg_pack(msg); + g_return_val_if_fail(raw != NULL, FALSE); + success = msim_send_raw(session, raw); + g_free(raw); + + msim_msg_dump("msim_msg_send()ing %s\n", msg); + + return success; +} + +/** + * + * Send a message to the server, whose contents is specified using + * variable arguments. + * + * @param session + * @param ... A sequence of gchar* key/type/value triplets, terminated with NULL. + * + * This function exists for coding convenience: it allows a message to be created + * and sent in one line of code. Internally it calls msim_msg_send(). + * + * IMPORTANT: See msim_msg_append() documentation for details on element types. + * + */ +gboolean +msim_send(MsimSession *session, ...) +{ + gboolean success; + MsimMessage *msg; + va_list argp; + + va_start(argp, session); + msg = msim_msg_new_v(NULL, argp); + + /* Actually send the message. */ + success = msim_msg_send(session, msg); + + /* Cleanup. */ + msim_msg_free(msg); + + return success; +} + +/** Create a new MsimMessageElement * - must be g_free()'d. + * + * For internal use; users probably want msim_msg_append() or msim_msg_insert_before(). + */ +static MsimMessageElement * +msim_msg_element_new(const gchar *name, MsimMessageType type, gpointer data) +{ + MsimMessageElement *elem; + + elem = g_new0(MsimMessageElement, 1); + + elem->name = name; + elem->type = type; + elem->data = data; + + return elem; +} + + +/** Append a new element to a message. + * + * @param name Textual name of element (static string, neither copied nor freed). + * @param type An MSIM_TYPE_* code. + * @param data Pointer to data, see below. + * + * @return The new message - must be assigned to as with GList*. For example: + * + * msg = msim_msg_append(msg, ...) + * + * The data parameter depends on the type given: + * + * * MSIM_TYPE_INTEGER: Use GUINT_TO_POINTER(x). + * + * * MSIM_TYPE_BINARY: Same as integer, non-zero is TRUE and zero is FALSE. + * + * * MSIM_TYPE_STRING: gchar *. The data WILL BE FREED - use g_strdup() if needed. + * + * * MSIM_TYPE_RAW: gchar *. The data WILL BE FREED - use g_strdup() if needed. + * + * * MSIM_TYPE_BINARY: g_string_new_len(data, length). The data AND GString will be freed. + * + * * MSIM_TYPE_DICTIONARY: An MsimMessage *. Freed when message is destroyed. + * + * * MSIM_TYPE_LIST: GList * of gchar *. Again, everything will be freed. + * + * */ +MsimMessage * +msim_msg_append(MsimMessage *msg, const gchar *name, + MsimMessageType type, gpointer data) +{ + return g_list_append(msg, msim_msg_element_new(name, type, data)); +} + +/** Insert a new element into a message, before the given element name. + * + * @param name_before Name of the element to insert the new element before. If + * could not be found or NULL, new element will be inserted at end. + * + * See msim_msg_append() for usage of other parameters, and an important note about return value. + */ +MsimMessage * +msim_msg_insert_before(MsimMessage *msg, const gchar *name_before, + const gchar *name, MsimMessageType type, gpointer data) +{ + MsimMessageElement *new_elem; + GList *node_before; + + new_elem = msim_msg_element_new(name, type, data); + + node_before = msim_msg_get_node(msg, name_before); + + return g_list_insert_before(msg, node_before, new_elem); +} + +/** Pack a string using the given GFunc and seperator. + * Used by msim_msg_dump() and msim_msg_pack(). + */ +gchar * +msim_msg_pack_using(MsimMessage *msg, + GFunc gf, + const gchar *sep, + const gchar *begin, const gchar *end) +{ + gchar **strings; + gchar **strings_tmp; + gchar *joined; + gchar *final; + int i; + + g_return_val_if_fail(msg != NULL, NULL); + + /* Add one for NULL terminator for g_strjoinv(). */ + strings = (gchar **)g_new0(gchar *, g_list_length(msg) + 1); + + strings_tmp = strings; + g_list_foreach(msg, gf, &strings_tmp); + + joined = g_strjoinv(sep, strings); + final = g_strconcat(begin, joined, end, NULL); + g_free(joined); + + /* Clean up. */ + for (i = 0; i < g_list_length(msg); ++i) { + g_free(strings[i]); + } + + g_free(strings); + + return final; +} +/** Store a human-readable string describing the element. + * + * @param data Pointer to an MsimMessageElement. + * @param user_data + */ +static void +msim_msg_debug_string_element(gpointer data, gpointer user_data) +{ + MsimMessageElement *elem; + gchar *string; + GString *gs; + gchar *binary; + gchar ***items; /* wow, a pointer to a pointer to a pointer */ + + gchar *s; + GList *gl; + guint i; + + elem = (MsimMessageElement *)data; + items = user_data; + + switch (elem->type) { + case MSIM_TYPE_INTEGER: + string = g_strdup_printf("%s(integer): %d", elem->name, + GPOINTER_TO_UINT(elem->data)); + break; + + case MSIM_TYPE_RAW: + string = g_strdup_printf("%s(raw): %s", elem->name, + elem->data ? (gchar *)elem->data : "(NULL)"); + break; + + case MSIM_TYPE_STRING: + string = g_strdup_printf("%s(string): %s", elem->name, + elem->data ? (gchar *)elem->data : "(NULL)"); + break; + + case MSIM_TYPE_BINARY: + gs = (GString *)elem->data; + binary = purple_base64_encode((guchar*)gs->str, gs->len); + string = g_strdup_printf("%s(binary, %d bytes): %s", elem->name, (int)gs->len, binary); + g_free(binary); + break; + + case MSIM_TYPE_BOOLEAN: + string = g_strdup_printf("%s(boolean): %s", elem->name, + elem->data ? "TRUE" : "FALSE"); + break; + + case MSIM_TYPE_DICTIONARY: + if (!elem->data) { + s = g_strdup("(NULL)"); + } else { + s = msim_msg_dump_to_str((MsimMessage *)elem->data); + } + + if (!s) { + s = g_strdup("(NULL, couldn't msim_msg_dump_to_str)"); + } + + string = g_strdup_printf("%s(dict): %s", elem->name, s); + + g_free(s); + break; + + case MSIM_TYPE_LIST: + gs = g_string_new(""); + g_string_append_printf(gs, "%s(list): \n", elem->name); + + i = 0; + for (gl = (GList *)elem->data; gl != NULL; gl = g_list_next(gl)) { + g_string_append_printf(gs, " %d. %s\n", i, (gchar *)(gl->data)); + ++i; + } + + string = gs->str; + break; + + default: + string = g_strdup_printf("%s(unknown type %d", + elem->name ? elem->name : "(NULL)", elem->type); + break; + } + + **items = string; + ++(*items); +} + +/** Print a human-readable string of the message to Purple's debug log. + * + * @param fmt_string A static string, in which '%s' will be replaced. + */ +void +msim_msg_dump(const gchar *fmt_string, MsimMessage *msg) +{ + gchar *debug_str; + + g_return_if_fail(fmt_string != NULL); + + debug_str = msim_msg_dump_to_str(msg); + + g_return_if_fail(debug_str != NULL); + + purple_debug_info("msim", fmt_string, debug_str); + + g_free(debug_str); +} + +/** Return a human-readable string of the message. + * + * @return A new gchar *, must be g_free()'d. + */ +gchar * +msim_msg_dump_to_str(MsimMessage *msg) +{ + gchar *debug_str; + + if (!msg) { + debug_str = g_strdup("<MsimMessage: empty>"); + } else { + debug_str = msim_msg_pack_using(msg, msim_msg_debug_string_element, + "\n", "<MsimMessage: \n", "\n/MsimMessage>"); + } + + return debug_str; +} + +/** Return a message element data as a new string for a raw protocol message, converting from other types (integer, etc.) if necessary. + * + * @return const gchar * The data as a string, or NULL. Caller must g_free(). + * + * Returns a string suitable for inclusion in a raw protocol message, not necessarily + * optimal for human consumption. For example, strings are escaped. Use + * msim_msg_get_string() if you want a string, which in some cases is same as this. + */ +gchar * +msim_msg_pack_element_data(MsimMessageElement *elem) +{ + GString *gs; + GList *gl; + + g_return_val_if_fail(elem != NULL, NULL); + + switch (elem->type) { + case MSIM_TYPE_INTEGER: + return g_strdup_printf("%d", GPOINTER_TO_UINT(elem->data)); + + case MSIM_TYPE_RAW: + /* Not un-escaped - this is a raw element, already escaped if necessary. */ + return (gchar *)g_strdup((gchar *)elem->data); + + case MSIM_TYPE_STRING: + /* Strings get escaped. msim_escape() creates a new string. */ + g_return_val_if_fail(elem->data != NULL, NULL); + return elem->data ? msim_escape((gchar *)elem->data) : + g_strdup("(NULL)"); + + case MSIM_TYPE_BINARY: + gs = (GString *)elem->data; + /* Do not escape! */ + return purple_base64_encode((guchar *)gs->str, gs->len); + + case MSIM_TYPE_BOOLEAN: + /* Not used by messages in the wire protocol * -- see msim_msg_pack_element. + * Only used by dictionaries, see msim_msg_pack_element_dict. */ + return elem->data ? g_strdup("On") : g_strdup("Off"); + + case MSIM_TYPE_DICTIONARY: + return msim_msg_pack_dict((MsimMessage *)elem->data); + + case MSIM_TYPE_LIST: + /* Pack using a|b|c|d|... */ + gs = g_string_new(""); + + for (gl = (GList *)elem->data; gl != NULL; gl = g_list_next(gl)) { + g_string_append_printf(gs, "%s", (gchar*)(gl->data)); + + /* All but last element is separated by a bar. */ + if (g_list_next(gl)) + g_string_append(gs, "|"); + } + + return gs->str; + + default: + purple_debug_info("msim", "field %s, unknown type %d\n", + elem->name ? elem->name : "(NULL)", + elem->type); + return NULL; + } +} + +/** Pack an element into its protcol representation inside a dictionary. + * + * See msim_msg_pack_element(). + */ +static void +msim_msg_pack_element_dict(gpointer data, gpointer user_data) +{ + MsimMessageElement *elem; + gchar *string, *data_string, ***items; + + elem = (MsimMessageElement *)data; + items = (gchar ***)user_data; + + /* Exclude elements beginning with '_' from packed protocol messages. */ + if (elem->name[0] == '_') { + return; + } + + data_string = msim_msg_pack_element_data(elem); + + g_return_if_fail(data_string != NULL); + + switch (elem->type) { + /* These types are represented by key name/value pairs (converted above). */ + case MSIM_TYPE_INTEGER: + case MSIM_TYPE_RAW: + case MSIM_TYPE_STRING: + case MSIM_TYPE_BINARY: + case MSIM_TYPE_DICTIONARY: + case MSIM_TYPE_LIST: + case MSIM_TYPE_BOOLEAN: /* Boolean is On or Off */ + string = g_strconcat(elem->name, "=", data_string, NULL); + break; + + default: + g_free(data_string); + g_return_if_fail(FALSE); + break; + } + + g_free(data_string); + + **items = string; + ++(*items); +} + +/** Pack an element into its protocol representation. + * + * @param data Pointer to an MsimMessageElement. + * @param user_data Pointer to a gchar ** array of string items. + * + * Called by msim_msg_pack(). Will pack the MsimMessageElement into + * a part of the protocol string and append it to the array. Caller + * is responsible for creating array to correct dimensions, and + * freeing each string element of the array added by this function. + */ +static void +msim_msg_pack_element(gpointer data, gpointer user_data) +{ + MsimMessageElement *elem; + gchar *string, *data_string; + gchar ***items; + + elem = (MsimMessageElement *)data; + items = (gchar ***)user_data; + + /* Exclude elements beginning with '_' from packed protocol messages. */ + if (elem->name[0] == '_') { + return; + } + + data_string = msim_msg_pack_element_data(elem); + + switch (elem->type) { + /* These types are represented by key name/value pairs (converted above). */ + case MSIM_TYPE_INTEGER: + case MSIM_TYPE_RAW: + case MSIM_TYPE_STRING: + case MSIM_TYPE_BINARY: + case MSIM_TYPE_DICTIONARY: + case MSIM_TYPE_LIST: + string = g_strconcat(elem->name, "\\", data_string, NULL); + break; + + /* Boolean is represented by absence or presence of name. */ + case MSIM_TYPE_BOOLEAN: + if (GPOINTER_TO_UINT(elem->data)) { + /* True - leave in, with blank value. */ + string = g_strdup_printf("%s\\", elem->name); + } else { + /* False - leave out. */ + string = g_strdup(""); + } + break; + + default: + g_free(data_string); + g_return_if_fail(FALSE); + break; + } + + g_free(data_string); + + **items = string; + ++(*items); +} + + +/** Return a packed string of a message suitable for sending over the wire. + * + * @return A string. Caller must g_free(). + */ +gchar * +msim_msg_pack(MsimMessage *msg) +{ + g_return_val_if_fail(msg != NULL, NULL); + + return msim_msg_pack_using(msg, msim_msg_pack_element, "\\", "\\", "\\final\\"); +} + +/** Return a packed string of a dictionary, suitable for embedding in MSIM_TYPE_DICTIONARY. + * + * @return A string; caller must g_free(). + */ +gchar * +msim_msg_pack_dict(MsimMessage *msg) +{ + g_return_val_if_fail(msg != NULL, NULL); + + return msim_msg_pack_using(msg, msim_msg_pack_element_dict, "\034", "", ""); +} + +/** + * Parse a raw protocol message string into a MsimMessage *. + * + * @param raw The raw message string to parse, will be g_free()'d. + * + * @return MsimMessage *. Caller should msim_msg_free() when done. + */ +MsimMessage * +msim_parse(gchar *raw) +{ + MsimMessage *msg; + gchar *token; + gchar **tokens; + gchar *key; + gchar *value; + int i; + + g_return_val_if_fail(raw != NULL, NULL); + + purple_debug_info("msim", "msim_parse: got <%s>\n", raw); + + key = NULL; + + /* All messages begin with a \. */ + if (raw[0] != '\\' || raw[1] == 0) { + purple_debug_info("msim", "msim_parse: incomplete/bad string, " + "missing initial backslash: <%s>\n", raw); + /* XXX: Should we try to recover, and read to first backslash? */ + + g_free(raw); + return NULL; + } + + msg = msim_msg_new(FALSE); + + for (tokens = g_strsplit(raw + 1, "\\", 0), i = 0; + (token = tokens[i]); + i++) { +#ifdef MSIM_DEBUG_PARSE + purple_debug_info("msim", "tok=<%s>, i%2=%d\n", token, i % 2); +#endif + if (i % 2) { + /* Odd-numbered ordinal is a value. */ + + value = token; + + /* Incoming protocol messages get tagged as MSIM_TYPE_RAW, which + * represents an untyped piece of data. msim_msg_get_* will + * convert to appropriate types for caller, and handle unescaping if needed. */ + msg = msim_msg_append(msg, g_strdup(key), MSIM_TYPE_RAW, g_strdup(value)); +#ifdef MSIM_DEBUG_PARSE + purple_debug_info("msim", "insert string: |%s|=|%s|\n", key, value); +#endif + } else { + /* Even numbered indexes are key names. */ + key = token; + } + } + g_strfreev(tokens); + + /* Can free now since all data was copied to hash key/values */ + g_free(raw); + + return msg; +} + +/** Search for and return the node in msg, matching name, or NULL. + * + * @param msg Message to search within. + * @param name Field name to search for. + * + * @return The GList * node for the MsimMessageElement with the given name, or NULL if not found or name is NULL. + * + * For internal use - users probably want to use msim_msg_get() to + * access the MsimMessageElement *, instead of the GList * container. + * + */ +static GList * +msim_msg_get_node(MsimMessage *msg, const gchar *name) +{ + GList *node; + + if (!name || !msg) { + return NULL; + } + + /* Linear search for the given name. O(n) but n is small. */ + for (node = msg; node != NULL; node = g_list_next(node)) { + MsimMessageElement *elem; + + elem = (MsimMessageElement *)node->data; + + g_return_val_if_fail(elem != NULL, NULL); + g_return_val_if_fail(elem->name != NULL, NULL); + + if (strcmp(elem->name, name) == 0) { + return node; + } + } + return NULL; +} + +/** Return the first MsimMessageElement * with given name in the MsimMessage *. + * + * @param name Name to search for. + * + * @return MsimMessageElement * matching name, or NULL. + * + * Note: useful fields of MsimMessageElement are 'data' and 'type', which + * you can access directly. But it is often more convenient to use + * another msim_msg_get_* that converts the data to what type you want. + */ +MsimMessageElement * +msim_msg_get(MsimMessage *msg, const gchar *name) +{ + GList *node; + + node = msim_msg_get_node(msg, name); + if (node) { + return (MsimMessageElement *)node->data; + } else { + return NULL; + } +} + +/** Return the data of an element of a given name, as a string. + * + * @param name Name of element. + * + * @return gchar * The data as a string, or NULL if not found. + * Caller must g_free(). + * + * Note that msim_msg_pack_element_data() is similar, but returns a string + * for inclusion into a raw protocol string (escaped and everything). + * This function unescapes the string for you, if needed. + */ +gchar * +msim_msg_get_string(MsimMessage *msg, const gchar *name) +{ + MsimMessageElement *elem; + + elem = msim_msg_get(msg, name); + if (!elem) { + return NULL; + } + + return msim_msg_get_string_from_element(elem); +} + +gchar * +msim_msg_get_string_from_element(MsimMessageElement *elem) +{ + g_return_val_if_fail(elem != NULL, NULL); + switch (elem->type) { + case MSIM_TYPE_INTEGER: + return g_strdup_printf("%d", GPOINTER_TO_UINT(elem->data)); + + case MSIM_TYPE_RAW: + /* Raw element from incoming message - if its a string, it'll + * be escaped. */ + return msim_unescape((gchar *)elem->data); + + case MSIM_TYPE_STRING: + /* Already unescaped. */ + return g_strdup((gchar *)elem->data); + + default: + purple_debug_info("msim", "msim_msg_get_string_element: type %d unknown, name %s\n", + elem->type, elem->name ? elem->name : "(NULL)"); + return NULL; + } +} + +/** Return an element as a new list. Caller frees with msim_msg_list_free(). */ +GList * +msim_msg_get_list(MsimMessage *msg, const gchar *name) +{ + MsimMessageElement *elem; + + elem = msim_msg_get(msg, name); + if (!elem) { + return NULL; + } + + return msim_msg_get_list_from_element(elem); +} + +GList * +msim_msg_get_list_from_element(MsimMessageElement *elem) +{ + g_return_val_if_fail(elem != NULL, NULL); + switch (elem->type) { + case MSIM_TYPE_LIST: + return msim_msg_list_copy((GList *)elem->data); + + case MSIM_TYPE_RAW: + return msim_msg_list_parse((gchar *)elem->data); + + default: + purple_debug_info("msim_msg_get_list", "type %d unknown, name %s\n", + elem->type, elem->name ? elem->name : "(NULL)"); + return NULL; + } +} + +/** + * Parse a \x1c-separated "dictionary" of key=value pairs into a hash table. + * + * @param raw The text of the dictionary to parse. Often the + * value for the 'body' field. + * + * @return A new MsimMessage *. Must msim_msg_free() when done. + */ +MsimMessage * +msim_msg_dictionary_parse(gchar *raw) +{ + MsimMessage *dict; + gchar *item; + gchar **items; + gchar **elements; + guint i; + + g_return_val_if_fail(raw != NULL, NULL); + + dict = msim_msg_new(NULL); + + for (items = g_strsplit(raw, "\x1c", 0), i = 0; + (item = items[i]); + i++) { + gchar *key, *value; + + elements = g_strsplit(item, "=", 2); + + key = elements[0]; + if (!key) { + purple_debug_info("msim", "msim_msg_parse_dictionary(%s): null key\n", + raw); + g_strfreev(elements); + break; + } + + value = elements[1]; + if (!value) { + purple_debug_info("msim", "msim_msg_parse_dictionary(%s): null value\n", + raw); + g_strfreev(elements); + break; + } + +#ifdef MSIM_DEBUG_PARSE + purple_debug_info("msim_msg_parse_dictionary","-- %s: %s\n", key ? key : "(NULL)", + value ? value : "(NULL)"); +#endif + /* TODO: free key; right now it is treated as static */ + dict = msim_msg_append(dict, g_strdup(key), MSIM_TYPE_RAW, g_strdup(value)); + + g_strfreev(elements); + } + + g_strfreev(items); + + return dict; +} + +/** Return an element as a new dictionary. Caller frees with msim_msg_free(). */ +MsimMessage * +msim_msg_get_dictionary(MsimMessage *msg, const gchar *name) +{ + MsimMessageElement *elem; + + elem = msim_msg_get(msg, name); + if (!elem) { + return NULL; + } + + return msim_msg_get_dictionary_from_element(elem); +} + +MsimMessage * +msim_msg_get_dictionary_from_element(MsimMessageElement *elem) +{ + g_return_val_if_fail(elem != NULL, NULL); + switch (elem->type) { + case MSIM_TYPE_DICTIONARY: + return msim_msg_clone((MsimMessage *)elem->data); + + case MSIM_TYPE_RAW: + return msim_msg_dictionary_parse((gchar *)elem->data); + + default: + purple_debug_info("msim_msg_get_dictionary", "type %d unknown, name %s\n", + elem->type, elem->name ? elem->name : "(NULL)"); + return NULL; + } +} + +/** Return the data of an element of a given name, as an unsigned integer. + * + * @param name Name of element. + * + * @return guint Numeric representation of data, or 0 if could not be converted / not found. + * + * Useful to obtain an element's data if you know it should be an integer, + * even if it is not stored as an MSIM_TYPE_INTEGER. MSIM_TYPE_STRING will + * be converted handled correctly, for example. + */ +guint +msim_msg_get_integer(MsimMessage *msg, const gchar *name) +{ + MsimMessageElement *elem; + + elem = msim_msg_get(msg, name); + + if (!elem) { + return 0; + } + + return msim_msg_get_integer_from_element(elem); +} + + +guint +msim_msg_get_integer_from_element(MsimMessageElement *elem) +{ + g_return_val_if_fail(elem != NULL, 0); + switch (elem->type) { + case MSIM_TYPE_INTEGER: + return GPOINTER_TO_UINT(elem->data); + + case MSIM_TYPE_RAW: + case MSIM_TYPE_STRING: + /* TODO: find out if we need larger integers */ + return (guint)atoi((gchar *)elem->data); + + default: + return 0; + } +} + +/** Return the data of an element of a given name, as a binary GString. + * + * @param binary_data A pointer to a new pointer, which will be filled in with the binary data. CALLER MUST g_free(). + * + * @param binary_length A pointer to an integer, which will be set to the binary data length. + * + * @return TRUE if successful, FALSE if not. + */ +gboolean +msim_msg_get_binary(MsimMessage *msg, const gchar *name, + gchar **binary_data, gsize *binary_length) +{ + MsimMessageElement *elem; + + elem = msim_msg_get(msg, name); + if (!elem) { + return FALSE; + } + + return msim_msg_get_binary_from_element(elem, binary_data, binary_length); +} + +gboolean +msim_msg_get_binary_from_element(MsimMessageElement *elem, gchar **binary_data, gsize *binary_length) +{ + GString *gs; + + g_return_val_if_fail(elem != NULL, FALSE); + + switch (elem->type) { + case MSIM_TYPE_RAW: + /* Incoming messages are tagged with MSIM_TYPE_RAW, and + * converted appropriately. They can still be "strings", just they won't + * be tagged as MSIM_TYPE_STRING (as MSIM_TYPE_STRING is intended to be used + * by msimprpl code for things like instant messages - stuff that should be + * escaped if needed). DWIM. + */ + + /* Previously, incoming messages were stored as MSIM_TYPE_STRING. + * This was fine for integers and strings, since they can easily be + * converted in msim_get_*, as desirable. However, it does not work + * well for binary strings. Consider: + * + * If incoming base64'd elements were tagged as MSIM_TYPE_STRING. + * msim_msg_get_binary() sees MSIM_TYPE_STRING, base64 decodes, returns. + * everything is fine. + * But then, msim_send() is called on the incoming message, which has + * a base64'd MSIM_TYPE_STRING that really is encoded binary. The values + * will be escaped since strings are escaped, and / becomes /2; no good. + * + */ + *binary_data = (gchar *)purple_base64_decode((const gchar *)elem->data, binary_length); + return TRUE; + + case MSIM_TYPE_BINARY: + gs = (GString *)elem->data; + + /* Duplicate data, so caller can g_free() it. */ + *binary_data = g_new0(char, gs->len); + memcpy(*binary_data, gs->str, gs->len); + + *binary_length = gs->len; + + return TRUE; + + + /* Rejected because if it isn't already a GString, have to g_new0 it and + * then caller has to ALSO free the GString! + * + * return (GString *)elem->data; */ + + default: + purple_debug_info("msim", "msim_msg_get_binary: unhandled type %d for key %s\n", + elem->type, elem->name ? elem->name : "(NULL)"); + return FALSE; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/myspace/message.h Wed Sep 12 19:11:38 2007 +0000 @@ -0,0 +1,117 @@ +/** MySpaceIM protocol messages + * + * \author Jeff Connelly + * + * Copyright (C) 2007, Jeff Connelly <jeff2@soc.pidgin.im> + * + * 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 _MYSPACE_MESSAGE_H +#define _MYSPACE_MESSAGE_H + +#include <glib.h> + +/* Types */ +#define MsimMessage GList /* #define instead of typedef to avoid casting */ +typedef struct _MsimMessageElement +{ + const gchar *name; /**< Textual name of element. */ + guint type; /**< MSIM_TYPE_* code. */ + gpointer data; /**< Pointer to data, or GUINT_TO_POINTER for int/bool. */ +} MsimMessageElement; + +typedef gchar MsimMessageType; + +#define msim_msg_get_next_element_node(msg) ((MsimMessage *)(msg->next)) + +/* Protocol field types */ +#define MSIM_TYPE_RAW '-' +#define MSIM_TYPE_INTEGER 'i' +#define MSIM_TYPE_STRING 's' +#define MSIM_TYPE_BINARY 'b' +#define MSIM_TYPE_BOOLEAN 'f' +#define MSIM_TYPE_DICTIONARY 'd' +#define MSIM_TYPE_LIST 'l' + +gchar *msim_escape(const gchar *msg); +gchar *msim_unescape(const gchar *msg); + +MsimMessage *msim_msg_new(gchar *first_key, ...); +/* No sentinel attribute, because can leave off varargs if not_empty is FALSE. */ + +MsimMessage *msim_msg_clone(MsimMessage *old); +void msim_msg_free_element_data(MsimMessageElement *elem); +void msim_msg_free(MsimMessage *msg); +MsimMessage *msim_msg_append(MsimMessage *msg, const gchar *name, MsimMessageType type, gpointer data); +MsimMessage *msim_msg_insert_before(MsimMessage *msg, const gchar *name_before, const gchar *name, MsimMessageType type, gpointer data); +gchar *msim_msg_dump_to_str(MsimMessage *msg); +gchar *msim_msg_pack_element_data(MsimMessageElement *elem); +void msim_msg_dump(const char *fmt_string, MsimMessage *msg); +gchar *msim_msg_pack(MsimMessage *msg); +gchar *msim_msg_pack_dict(MsimMessage *msg); + +GList *msim_msg_list_copy(GList *old); +void msim_msg_list_free(GList *l); +GList *msim_msg_list_parse(const gchar *raw); + +/* Defined in myspace.h */ +struct _MsimSession; + +/* Based on http://permalink.gmane.org/gmane.comp.parsers.sparse/695 + * Define macros for useful gcc attributes. */ +#ifdef __GNUC__ +#define GCC_VERSION (__GNUC__ * 1000 + __GNUC_MINOR__) +#define FORMAT_ATTR(pos) __attribute__ ((__format__ (__printf__, pos, pos+1))) +#define NORETURN_ATTR __attribute__ ((__noreturn__)) +/* __sentinel__ attribute was introduced in gcc 3.5 */ +#if (GCC_VERSION >= 3005) + #define SENTINEL_ATTR __attribute__ ((__sentinel__(0))) +#else + #define SENTINEL_ATTR +#endif /* gcc >= 3.5 */ +#else + #define FORMAT_ATTR(pos) + #define NORETURN_ATTR + #define SENTINEL_ATTR +#endif + +/* Cause gcc to emit "a missing sentinel in function call" if forgot + * to write NULL as last, terminating parameter. */ +gboolean msim_send(struct _MsimSession *session, ...) SENTINEL_ATTR; + +gboolean msim_msg_send(struct _MsimSession *session, MsimMessage *msg); + +MsimMessage *msim_parse(gchar *raw); +MsimMessage *msim_msg_dictionary_parse(gchar *raw); + +MsimMessageElement *msim_msg_get(MsimMessage *msg, const gchar *name); + +/* Retrieve data by name */ +gchar *msim_msg_get_string(MsimMessage *msg, const gchar *name); +GList *msim_msg_get_list(MsimMessage *msg, const gchar *name); +MsimMessage *msim_msg_get_dictionary(MsimMessage *msg, const gchar *name); +guint msim_msg_get_integer(MsimMessage *msg, const gchar *name); +gboolean msim_msg_get_binary(MsimMessage *msg, const gchar *name, gchar **binary_data, gsize *binary_length); + +/* Retrieve data by element (MsimMessageElement *), returned from msim_msg_get() */ +gchar *msim_msg_get_string_from_element(MsimMessageElement *elem); +GList *msim_msg_get_list_from_element(MsimMessageElement *elem); +MsimMessage *msim_msg_get_dictionary_from_element(MsimMessageElement *elem); +guint msim_msg_get_integer_from_element(MsimMessageElement *elem); +gboolean msim_msg_get_binary_from_element(MsimMessageElement *elem, + gchar **binary_data, gsize *binary_length); + +#endif /* _MYSPACE_MESSAGE_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/myspace/myspace.c Wed Sep 12 19:11:38 2007 +0000 @@ -0,0 +1,3304 @@ +/* MySpaceIM Protocol Plugin + * + * \author Jeff Connelly + * + * Copyright (C) 2007, Jeff Connelly <jeff2@soc.pidgin.im> + * + * Based on Purple's "C Plugin HOWTO" hello world example. + * + * Code also drawn from mockprpl: + * http://snarfed.org/space/purple+mock+protocol+plugin + * Copyright (C) 2004-2007, Ryan Barrett <mockprpl@ryanb.org> + * + * and some constructs also based on existing Purple plugins, which are: + * Copyright (C) 2003, Robbert Haarman <purple@inglorion.net> + * Copyright (C) 2003, Ethan Blanton <eblanton@cs.purdue.edu> + * Copyright (C) 2000-2003, Rob Flynn <rob@tgflinux.com> + * Copyright (C) 1998-1999, Mark Spencer <markster@marko.net> + * + * 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 + */ + +#define PURPLE_PLUGIN + +#include "myspace.h" + +/* Internal functions */ + +#ifdef MSIM_DEBUG_MSG +static void print_hash_item(gpointer key, gpointer value, gpointer user_data); +#endif + +static int msim_send_really_raw(PurpleConnection *gc, const char *buf, int total_bytes); +static gboolean msim_login_challenge(MsimSession *session, MsimMessage *msg); +static const gchar *msim_compute_login_response(const gchar nonce[2 * NONCE_SIZE], const gchar *email, const gchar *password, guint *response_len); + +static gboolean msim_incoming_bm_record_cv(MsimSession *session, MsimMessage *msg); +static gboolean msim_incoming_bm(MsimSession *session, MsimMessage *msg); +static gboolean msim_incoming_status(MsimSession *session, MsimMessage *msg); +static gboolean msim_incoming_im(MsimSession *session, MsimMessage *msg); +/* static gboolean msim_incoming_zap(MsimSession *session, MsimMessage *msg); - in zap.c */ +static gboolean msim_incoming_action(MsimSession *session, MsimMessage *msg); +static gboolean msim_incoming_media(MsimSession *session, MsimMessage *msg); +static gboolean msim_incoming_unofficial_client(MsimSession *session, + MsimMessage *msg); + +#ifdef MSIM_SEND_CLIENT_VERSION +static gboolean msim_send_unofficial_client(MsimSession *session, gchar *username); +#endif + +static void msim_get_info_cb(MsimSession *session, MsimMessage *userinfo, gpointer data); + +static void msim_set_status_code(MsimSession *session, guint code, gchar *statstring); + +static gboolean msim_process_server_info(MsimSession *session, MsimMessage *msg); +static gboolean msim_web_challenge(MsimSession *session, MsimMessage *msg); +static gboolean msim_process_reply(MsimSession *session, MsimMessage *msg); + +static gboolean msim_preprocess_incoming(MsimSession *session, MsimMessage *msg); + +#ifdef MSIM_USE_KEEPALIVE +static gboolean msim_check_alive(gpointer data); +#endif + +static gboolean msim_we_are_logged_on(MsimSession *session, MsimMessage *msg); + +static gboolean msim_process(MsimSession *session, MsimMessage *msg); + +static MsimMessage *msim_do_postprocessing(MsimMessage *msg, const gchar *uid_field_name, const gchar *uid_before, guint uid); +static void msim_postprocess_outgoing_cb(MsimSession *session, MsimMessage *userinfo, gpointer data); +static gboolean msim_postprocess_outgoing(MsimSession *session, MsimMessage *msg, const gchar *username, const gchar *uid_field_name, const gchar *uid_before); + +static gboolean msim_error(MsimSession *session, MsimMessage *msg); + +static void msim_check_inbox_cb(MsimSession *session, MsimMessage *userinfo, gpointer data); +static gboolean msim_check_inbox(gpointer data); + +static void msim_input_cb(gpointer gc_uncasted, gint source, PurpleInputCondition cond); + + +static void msim_connect_cb(gpointer data, gint source, const gchar *error_message); + +static void msim_import_friends(PurplePluginAction *action); +static void msim_import_friends_cb(MsimSession *session, MsimMessage *reply, gpointer user_data); +static gboolean msim_get_contact_list(MsimSession *session, int what_to_do_after); + +static gboolean msim_uri_handler(const gchar *proto, const gchar *cmd, GHashTable *params); +static void msim_uri_handler_addContact_cb(MsimSession *session, MsimMessage *userinfo, gpointer data); +static void msim_uri_handler_sendIM_cb(MsimSession *session, MsimMessage *userinfo, gpointer data); + +/** + * Load the plugin. + */ +gboolean +msim_load(PurplePlugin *plugin) +{ + /* If compiled to use RC4 from libpurple, check if it is really there. */ + if (!purple_ciphers_find_cipher("rc4")) { + purple_debug_error("msim", "rc4 not in libpurple, but it is required - not loading MySpaceIM plugin!\n"); + purple_notify_error(plugin, _("Missing Cipher"), + _("The RC4 cipher could not be found"), + _("Upgrade " + "to a libpurple with RC4 support (>= 2.0.1). MySpaceIM " + "plugin will not be loaded.")); + return FALSE; + } + return TRUE; +} + +/** + * Get possible user status types. Based on mockprpl. + * + * @return GList of status types. + */ +GList * +msim_status_types(PurpleAccount *acct) +{ + GList *types; + PurpleStatusType *status; + + purple_debug_info("myspace", "returning status types\n"); + + types = NULL; + + /* Statuses are almost all the same. Define a macro to reduce code repetition. */ +#define _MSIM_ADD_NEW_STATUS(prim) status = \ + purple_status_type_new_with_attrs( \ + prim, /* PurpleStatusPrimitive */ \ + NULL, /* id - use default */ \ + NULL, /* name - use default */ \ + TRUE, /* savable */ \ + TRUE, /* user_settable */ \ + FALSE, /* not independent */ \ + \ + /* Attributes - each status can have a message. */ \ + "message", \ + _("Message"), \ + purple_value_new(PURPLE_TYPE_STRING), \ + NULL); \ + \ + \ + types = g_list_append(types, status) + + + _MSIM_ADD_NEW_STATUS(PURPLE_STATUS_AVAILABLE); + _MSIM_ADD_NEW_STATUS(PURPLE_STATUS_AWAY); + _MSIM_ADD_NEW_STATUS(PURPLE_STATUS_OFFLINE); + _MSIM_ADD_NEW_STATUS(PURPLE_STATUS_INVISIBLE); + + + return types; +} + +/** + * Return the icon name for a buddy and account. + * + * @param acct The account to find the icon for, or NULL for protocol icon. + * @param buddy The buddy to find the icon for, or NULL for the account icon. + * + * @return The base icon name string. + */ +const gchar * +msim_list_icon(PurpleAccount *acct, PurpleBuddy *buddy) +{ + /* Use a MySpace icon submitted by hbons at + * http://developer.pidgin.im/wiki/MySpaceIM. */ + return "myspace"; +} + +#ifdef MSIM_DEBUG_MSG +static void +print_hash_item(gpointer key, gpointer value, gpointer user_data) +{ + purple_debug_info("msim", "%s=%s\n", + key ? (gchar *)key : "(NULL)", + value ? (gchar *)value : "(NULL)"); +} +#endif + +/** + * Send raw data (given as a NUL-terminated string) to the server. + * + * @param session + * @param msg The raw data to send, in a NUL-terminated string. + * + * @return TRUE if succeeded, FALSE if not. + * + */ +gboolean +msim_send_raw(MsimSession *session, const gchar *msg) +{ + g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); + g_return_val_if_fail(msg != NULL, FALSE); + + purple_debug_info("msim", "msim_send_raw: writing <%s>\n", msg); + + return msim_send_really_raw(session->gc, msg, strlen(msg)) == + strlen(msg); +} + +/** Send raw data to the server, possibly with embedded NULs. + * + * Used in prpl_info struct, so that plugins can have the most possible + * control of what is sent over the connection. Inside this prpl, + * msim_send_raw() is used, since it sends NUL-terminated strings (easier). + * + * @param gc PurpleConnection + * @param buf Buffer to send + * @param total_bytes Size of buffer to send + * + * @return Bytes successfully sent, or -1 on error. + */ +static int +msim_send_really_raw(PurpleConnection *gc, const char *buf, int total_bytes) +{ + int total_bytes_sent; + MsimSession *session; + + g_return_val_if_fail(gc != NULL, -1); + g_return_val_if_fail(buf != NULL, -1); + g_return_val_if_fail(total_bytes >= 0, -1); + + session = (MsimSession *)gc->proto_data; + + g_return_val_if_fail(MSIM_SESSION_VALID(session), -1); + + /* Loop until all data is sent, or a failure occurs. */ + total_bytes_sent = 0; + do { + int bytes_sent; + + bytes_sent = send(session->fd, buf + total_bytes_sent, + total_bytes - total_bytes_sent, 0); + + if (bytes_sent < 0) { + purple_debug_info("msim", "msim_send_raw(%s): send() failed: %s\n", + buf, g_strerror(errno)); + return total_bytes_sent; + } + total_bytes_sent += bytes_sent; + + } while(total_bytes_sent < total_bytes); + + return total_bytes_sent; +} + + +/** + * Start logging in to the MSIM servers. + * + * @param acct Account information to use to login. + */ +void +msim_login(PurpleAccount *acct) +{ + PurpleConnection *gc; + const gchar *host; + int port; + + g_return_if_fail(acct != NULL); + g_return_if_fail(acct->username != NULL); + + purple_debug_info("msim", "logging in %s\n", acct->username); + + gc = purple_account_get_connection(acct); + gc->proto_data = msim_session_new(acct); + gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_NO_URLDESC; + +#ifdef MSIM_MAX_PASSWORD_LENGTH + /* Passwords are limited in length. */ + if (strlen(acct->password) > MSIM_MAX_PASSWORD_LENGTH) { + gchar *str; + + str = g_strdup_printf( + _("Sorry, passwords over %d characters in length (yours is " + "%d) are not supported by MySpace."), + MSIM_MAX_PASSWORD_LENGTH, + (int)strlen(acct->password)); + + /* Notify an error message also, because this is important! */ + purple_notify_error(acct, g_strdup(_("MySpaceIM Error")), str, NULL); + + purple_connection_error(gc, str); + + g_free(str); + } +#endif + + /* 1. connect to server */ + purple_connection_update_progress(gc, _("Connecting"), + 0, /* which connection step this is */ + 4); /* total number of steps */ + + host = purple_account_get_string(acct, "server", MSIM_SERVER); + port = purple_account_get_int(acct, "port", MSIM_PORT); + + /* From purple.sf.net/api: + * """Note that this function name can be misleading--although it is called + * "proxy connect," it is used for establishing any outgoing TCP connection, + * whether through a proxy or not.""" */ + + /* Calls msim_connect_cb when connected. */ + if (!purple_proxy_connect(gc, acct, host, port, msim_connect_cb, gc)) { + /* TODO: try other ports if in auto mode, then save + * working port and try that first next time. */ + purple_connection_error(gc, _("Couldn't create socket")); + return; + } +} + +/** + * Process a login challenge, sending a response. + * + * @param session + * @param msg Login challenge message. + * + * @return TRUE if successful, FALSE if not + */ +static gboolean +msim_login_challenge(MsimSession *session, MsimMessage *msg) +{ + PurpleAccount *account; + const gchar *response; + guint response_len; + gchar *nc; + gsize nc_len; + + g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); + g_return_val_if_fail(msg != NULL, FALSE); + + g_return_val_if_fail(msim_msg_get_binary(msg, "nc", &nc, &nc_len), FALSE); + + account = session->account; + + g_return_val_if_fail(account != NULL, FALSE); + + purple_connection_update_progress(session->gc, _("Reading challenge"), 1, 4); + + purple_debug_info("msim", "nc is %d bytes, decoded\n", nc_len); + + if (nc_len != MSIM_AUTH_CHALLENGE_LENGTH) { + purple_debug_info("msim", "bad nc length: %x != 0x%x\n", nc_len, MSIM_AUTH_CHALLENGE_LENGTH); + purple_connection_error(session->gc, _("Unexpected challenge length from server")); + return FALSE; + } + + purple_connection_update_progress(session->gc, _("Logging in"), 2, 4); + + response_len = 0; + response = msim_compute_login_response(nc, account->username, account->password, &response_len); + + g_free(nc); + + return msim_send(session, + "login2", MSIM_TYPE_INTEGER, MSIM_AUTH_ALGORITHM, + /* This is actually user's email address. */ + "username", MSIM_TYPE_STRING, g_strdup(account->username), + /* GString and gchar * response will be freed in msim_msg_free() in msim_send(). */ + "response", MSIM_TYPE_BINARY, g_string_new_len(response, response_len), + "clientver", MSIM_TYPE_INTEGER, MSIM_CLIENT_VERSION, + "langid", MSIM_TYPE_INTEGER, MSIM_LANGUAGE_ID_ENGLISH, + "imlang", MSIM_TYPE_STRING, g_strdup(MSIM_LANGUAGE_NAME_ENGLISH), + "reconn", MSIM_TYPE_INTEGER, 0, + "status", MSIM_TYPE_INTEGER, 100, + "id", MSIM_TYPE_INTEGER, 1, + NULL); +} + +/** + * Compute the base64'd login challenge response based on username, password, nonce, and IPs. + * + * @param nonce The base64 encoded nonce ('nc') field from the server. + * @param email User's email address (used as login name). + * @param password User's cleartext password. + * @param response_len Will be written with response length. + * + * @return Binary login challenge response, ready to send to the server. + * Must be g_free()'d when finished. NULL if error. + */ +static const gchar * +msim_compute_login_response(const gchar nonce[2 * NONCE_SIZE], + const gchar *email, const gchar *password, guint *response_len) +{ + PurpleCipherContext *key_context; + PurpleCipher *sha1; + PurpleCipherContext *rc4; + + guchar hash_pw[HASH_SIZE]; + guchar key[HASH_SIZE]; + gchar *password_utf16le, *password_utf8_lc; + guchar *data; + guchar *data_out; + size_t data_len, data_out_len; + gsize conv_bytes_read, conv_bytes_written; + GError *conv_error; +#ifdef MSIM_DEBUG_LOGIN_CHALLENGE + int i; +#endif + + g_return_val_if_fail(nonce != NULL, NULL); + g_return_val_if_fail(email != NULL, NULL); + g_return_val_if_fail(password != NULL, NULL); + g_return_val_if_fail(response_len != NULL, NULL); + + /* Convert password to lowercase (required for passwords containing + * uppercase characters). MySpace passwords are lowercase, + * see ticket #2066. */ + password_utf8_lc = g_utf8_strdown(password, -1); + + /* Convert ASCII password to UTF16 little endian */ + purple_debug_info("msim", "converting password to UTF-16LE\n"); + conv_error = NULL; + password_utf16le = g_convert(password_utf8_lc, -1, "UTF-16LE", "UTF-8", + &conv_bytes_read, &conv_bytes_written, &conv_error); + g_free(password_utf8_lc); + + g_return_val_if_fail(conv_bytes_read == strlen(password), NULL); + + if (conv_error != NULL) { + purple_debug_error("msim", + "g_convert password UTF8->UTF16LE failed: %s", + conv_error->message); + g_error_free(conv_error); + return NULL; + } + + /* Compute password hash */ + purple_cipher_digest_region("sha1", (guchar *)password_utf16le, + conv_bytes_written, sizeof(hash_pw), hash_pw, NULL); + g_free(password_utf16le); + +#ifdef MSIM_DEBUG_LOGIN_CHALLENGE + purple_debug_info("msim", "pwhash = "); + for (i = 0; i < sizeof(hash_pw); i++) + purple_debug_info("msim", "%.2x ", hash_pw[i]); + purple_debug_info("msim", "\n"); +#endif + + /* key = sha1(sha1(pw) + nonce2) */ + sha1 = purple_ciphers_find_cipher("sha1"); + key_context = purple_cipher_context_new(sha1, NULL); + purple_cipher_context_append(key_context, hash_pw, HASH_SIZE); + purple_cipher_context_append(key_context, (guchar *)(nonce + NONCE_SIZE), NONCE_SIZE); + purple_cipher_context_digest(key_context, sizeof(key), key, NULL); + +#ifdef MSIM_DEBUG_LOGIN_CHALLENGE + purple_debug_info("msim", "key = "); + for (i = 0; i < sizeof(key); i++) { + purple_debug_info("msim", "%.2x ", key[i]); + } + purple_debug_info("msim", "\n"); +#endif + + rc4 = purple_cipher_context_new_by_name("rc4", NULL); + + /* Note: 'key' variable is 0x14 bytes (from SHA-1 hash), + * but only first 0x10 used for the RC4 key. */ + purple_cipher_context_set_option(rc4, "key_len", (gpointer)0x10); + purple_cipher_context_set_key(rc4, key); + + /* TODO: obtain IPs of network interfaces */ + + /* rc4 encrypt: + * nonce1+email+IP list */ + + data_len = NONCE_SIZE + strlen(email) + MSIM_LOGIN_IP_LIST_LEN; + data = g_new0(guchar, data_len); + memcpy(data, nonce, NONCE_SIZE); + memcpy(data + NONCE_SIZE, email, strlen(email)); + memcpy(data + NONCE_SIZE + strlen(email), MSIM_LOGIN_IP_LIST, MSIM_LOGIN_IP_LIST_LEN); + + data_out = g_new0(guchar, data_len); + + purple_cipher_context_encrypt(rc4, (const guchar *)data, + data_len, data_out, &data_out_len); + purple_cipher_context_destroy(rc4); + + g_assert(data_out_len == data_len); + +#ifdef MSIM_DEBUG_LOGIN_CHALLENGE + purple_debug_info("msim", "response=<%s>\n", data_out); +#endif + + *response_len = data_out_len; + + return (const gchar *)data_out; +} + +/** + * Schedule an IM to be sent once the user ID is looked up. + * + * @param gc Connection. + * @param who A user id, email, or username to send the message to. + * @param message Instant message text to send. + * @param flags Flags. + * + * @return 1 if successful or postponed, -1 if failed + * + * Allows sending to a user by username, email address, or userid. If + * a username or email address is given, the userid must be looked up. + * This function does that by calling msim_postprocess_outgoing(). + */ +int +msim_send_im(PurpleConnection *gc, const gchar *who, const gchar *message, + PurpleMessageFlags flags) +{ + MsimSession *session; + gchar *message_msim; + int rc; + + g_return_val_if_fail(gc != NULL, -1); + g_return_val_if_fail(who != NULL, -1); + g_return_val_if_fail(message != NULL, -1); + + /* 'flags' has many options, not used here. */ + + session = (MsimSession *)gc->proto_data; + + g_return_val_if_fail(MSIM_SESSION_VALID(session), -1); + + message_msim = html_to_msim_markup(session, message); + + if (msim_send_bm(session, who, message_msim, MSIM_BM_INSTANT)) { + /* Return 1 to have Purple show this IM as being sent, 0 to not. I always + * return 1 even if the message could not be sent, since I don't know if + * it has failed yet--because the IM is only sent after the userid is + * retrieved from the server (which happens after this function returns). + */ + /* TODO: maybe if message is delayed, don't echo to conv window, + * but do echo it to conv window manually once it is actually + * sent? Would be complicated. */ + rc = 1; + } else { + rc = -1; + } + + g_free(message_msim); + + /* + * In MySpace, you login with your email address, but don't talk to other + * users using their email address. So there is currently an asymmetry in the + * IM windows when using this plugin: + * + * you@example.com: hello + * some_other_user: what's going on? + * you@example.com: just coding a prpl + * + * TODO: Make the sent IM's appear as from the user's username, instead of + * their email address. Purple uses the login (in MSIM, the email)--change this. + */ + + return rc; +} + +/** Send a buddy message of a given type. + * + * @param session + * @param who Username to send message to. + * @param text Message text to send. Not freed; will be copied. + * @param type A MSIM_BM_* constant. + * + * @return TRUE if success, FALSE if fail. + * + * Buddy messages ('bm') include instant messages, action messages, status messages, etc. + * + */ +gboolean +msim_send_bm(MsimSession *session, const gchar *who, const gchar *text, + int type) +{ + gboolean rc; + MsimMessage *msg; + const gchar *from_username; + + g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); + g_return_val_if_fail(who != NULL, FALSE); + g_return_val_if_fail(text != NULL, FALSE); + + from_username = session->account->username; + + g_return_val_if_fail(from_username != NULL, FALSE); + + purple_debug_info("msim", "sending %d message from %s to %s: %s\n", + type, from_username, who, text); + + msg = msim_msg_new( + "bm", MSIM_TYPE_INTEGER, GUINT_TO_POINTER(type), + "sesskey", MSIM_TYPE_INTEGER, GUINT_TO_POINTER(session->sesskey), + /* 't' will be inserted here */ + "cv", MSIM_TYPE_INTEGER, GUINT_TO_POINTER(MSIM_CLIENT_VERSION), + "msg", MSIM_TYPE_STRING, g_strdup(text), + NULL); + + rc = msim_postprocess_outgoing(session, msg, who, "t", "cv"); + + msim_msg_free(msg); + + return rc; +} + + +/** Record the client version in the buddy list, from an incoming message. */ +static gboolean +msim_incoming_bm_record_cv(MsimSession *session, MsimMessage *msg) +{ + gchar *username, *cv; + gboolean ret; + MsimUser *user; + + username = msim_msg_get_string(msg, "_username"); + cv = msim_msg_get_string(msg, "cv"); + + g_return_val_if_fail(username != NULL, FALSE); + if (!cv) { + /* No client version to record, don't worry about it. */ + return FALSE; + } + + user = msim_find_user(session, username); + + if (user) { + user->client_cv = atol(cv); + ret = TRUE; + } else { + ret = FALSE; + } + + g_free(username); + g_free(cv); + + return ret; +} + +/** Handle an incoming buddy message. */ +static gboolean +msim_incoming_bm(MsimSession *session, MsimMessage *msg) +{ + guint bm; + + bm = msim_msg_get_integer(msg, "bm"); + + msim_incoming_bm_record_cv(session, msg); + + switch (bm) { + case MSIM_BM_STATUS: + return msim_incoming_status(session, msg); + case MSIM_BM_INSTANT: + return msim_incoming_im(session, msg); + case MSIM_BM_ACTION: + return msim_incoming_action(session, msg); + case MSIM_BM_MEDIA: + return msim_incoming_media(session, msg); + case MSIM_BM_UNOFFICIAL_CLIENT: + return msim_incoming_unofficial_client(session, msg); + default: + /* Not really an IM, but show it for informational + * purposes during development. */ + return msim_incoming_im(session, msg); + } +} + +/** + * Handle an incoming instant message. + * + * @param session The session + * @param msg Message from the server, containing 'f' (userid from) and 'msg'. + * Should also contain username in _username from preprocessing. + * + * @return TRUE if successful. + */ +static gboolean +msim_incoming_im(MsimSession *session, MsimMessage *msg) +{ + gchar *username, *msg_msim_markup, *msg_purple_markup; + time_t time_received; + + g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); + g_return_val_if_fail(msg != NULL, FALSE); + + username = msim_msg_get_string(msg, "_username"); + g_return_val_if_fail(username != NULL, FALSE); + + msg_msim_markup = msim_msg_get_string(msg, "msg"); + g_return_val_if_fail(msg_msim_markup != NULL, FALSE); + + msg_purple_markup = msim_markup_to_html(session, msg_msim_markup); + g_free(msg_msim_markup); + + time_received = msim_msg_get_integer(msg, "date"); + if (!time_received) { + time_received = time(NULL); + } + + serv_got_im(session->gc, username, msg_purple_markup, PURPLE_MESSAGE_RECV, time_received); + + g_free(username); + g_free(msg_purple_markup); + + return TRUE; +} + +/** + * Process unrecognized information. + * + * @param session + * @param msg An MsimMessage that was unrecognized, or NULL. + * @param note Information on what was unrecognized, or NULL. + */ +void +msim_unrecognized(MsimSession *session, MsimMessage *msg, gchar *note) +{ + /* TODO: Some more context, outwardly equivalent to a backtrace, + * for helping figure out what this msg is for. What was going on? + * But not too much information so that a user + * posting this dump reveals confidential information. + */ + + /* TODO: dump unknown msgs to file, so user can send them to me + * if they wish, to help add support for new messages (inspired + * by Alexandr Shutko, who maintains OSCAR protocol documentation). */ + + purple_debug_info("msim", "Unrecognized data on account for %s\n", + (session && session->account && session->account->username) ? + session->account->username : "(NULL)"); + if (note) { + purple_debug_info("msim", "(Note: %s)\n", note); + } + + if (msg) { + msim_msg_dump("Unrecognized message dump: %s\n", msg); + } +} + +/** + * Handle an incoming action message. + * + * @param session + * @param msg + * + * @return TRUE if successful. + * + */ +static gboolean +msim_incoming_action(MsimSession *session, MsimMessage *msg) +{ + gchar *msg_text, *username; + gboolean rc; + + g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); + g_return_val_if_fail(msg != NULL, FALSE); + + msg_text = msim_msg_get_string(msg, "msg"); + g_return_val_if_fail(msg_text != NULL, FALSE); + + username = msim_msg_get_string(msg, "_username"); + g_return_val_if_fail(username != NULL, FALSE); + + purple_debug_info("msim", "msim_incoming_action: action <%s> from <%d>\n", + msg_text, username); + + if (g_str_equal(msg_text, "%typing%")) { + /* TODO: find out if msim repeatedly sends typing messages, so we can + * give it a timeout. Right now, there does seem to be an inordinately + * amount of time between typing stopped-typing notifications. */ + serv_got_typing(session->gc, username, 0, PURPLE_TYPING); + rc = TRUE; + } else if (g_str_equal(msg_text, "%stoptyping%")) { + serv_got_typing_stopped(session->gc, username); + rc = TRUE; + } else if (strstr(msg_text, "!!!ZAP_SEND!!!=RTE_BTN_ZAPS_")) { + rc = msim_incoming_zap(session, msg); + } else { + msim_unrecognized(session, msg, + "got to msim_incoming_action but unrecognized value for 'msg'"); + rc = FALSE; + } + + g_free(msg_text); + g_free(username); + + return rc; +} + +/* Process an incoming media (message background?) message. */ +static gboolean +msim_incoming_media(MsimSession *session, MsimMessage *msg) +{ + gchar *username, *text; + + username = msim_msg_get_string(msg, "_username"); + text = msim_msg_get_string(msg, "msg"); + + g_return_val_if_fail(username != NULL, FALSE); + g_return_val_if_fail(text != NULL, FALSE); + + purple_debug_info("msim", "msim_incoming_media: from %s, got msg=%s\n", username, text); + + /* Media messages are sent when the user opens a window to someone. + * Tell libpurple they started typing and stopped typing, to inform the Psychic + * Mode plugin so it too can open a window to the user. */ + serv_got_typing(session->gc, username, 0, PURPLE_TYPING); + serv_got_typing_stopped(session->gc, username); + + g_free(username); + + return TRUE; +} + +/* Process an incoming "unofficial client" message. The plugin for + * Miranda IM sends this message with the plugin information. */ +static gboolean +msim_incoming_unofficial_client(MsimSession *session, MsimMessage *msg) +{ + MsimUser *user; + gchar *username, *client_info; + + username = msim_msg_get_string(msg, "_username"); + client_info = msim_msg_get_string(msg, "msg"); + + g_return_val_if_fail(username != NULL, FALSE); + g_return_val_if_fail(client_info != NULL, FALSE); + + purple_debug_info("msim", "msim_incoming_unofficial_client: %s is using client %s\n", + username, client_info); + + user = msim_find_user(session, username); + + g_return_val_if_fail(user != NULL, FALSE); + + if (user->client_info) { + g_free(user->client_info); + } + user->client_info = client_info; + + g_free(username); + /* Do not free client_info - the MsimUser now owns it. */ + + return TRUE; +} + + +#ifdef MSIM_SEND_CLIENT_VERSION +/** Send our client version to another unofficial client that understands it. */ +static gboolean +msim_send_unofficial_client(MsimSession *session, gchar *username) +{ + gchar *our_info; + gboolean ret; + + our_info = g_strdup_printf("Libpurple %d.%d.%d - msimprpl %s", + PURPLE_MAJOR_VERSION, + PURPLE_MINOR_VERSION, + PURPLE_MICRO_VERSION, + MSIM_PRPL_VERSION_STRING); + + ret = msim_send_bm(session, username, our_info, MSIM_BM_UNOFFICIAL_CLIENT); + + return ret; +} +#endif + +/** + * Handle when our user starts or stops typing to another user. + * + * @param gc + * @param name The buddy name to which our user is typing to + * @param state PURPLE_TYPING, PURPLE_TYPED, PURPLE_NOT_TYPING + * + * @return 0 + */ +unsigned int +msim_send_typing(PurpleConnection *gc, const gchar *name, + PurpleTypingState state) +{ + const gchar *typing_str; + MsimSession *session; + + g_return_val_if_fail(gc != NULL, 0); + g_return_val_if_fail(name != NULL, 0); + + session = (MsimSession *)gc->proto_data; + + g_return_val_if_fail(MSIM_SESSION_VALID(session), 0); + + switch (state) { + case PURPLE_TYPING: + typing_str = "%typing%"; + break; + + case PURPLE_TYPED: + case PURPLE_NOT_TYPING: + default: + typing_str = "%stoptyping%"; + break; + } + + purple_debug_info("msim", "msim_send_typing(%s): %d (%s)\n", name, state, typing_str); + msim_send_bm(session, name, typing_str, MSIM_BM_ACTION); + return 0; +} + + + +/** Callback for msim_get_info(), for when user info is received. */ +static void +msim_get_info_cb(MsimSession *session, MsimMessage *user_info_msg, + gpointer data) +{ + MsimMessage *msg; + gchar *username; + PurpleNotifyUserInfo *user_info; + MsimUser *user; + gboolean temporary_user; + + g_return_if_fail(MSIM_SESSION_VALID(session)); + + /* Get user{name,id} from msim_get_info, passed as an MsimMessage for + orthogonality. */ + msg = (MsimMessage *)data; + g_return_if_fail(msg != NULL); + + username = msim_msg_get_string(msg, "user"); + if (!username) { + purple_debug_info("msim", "msim_get_info_cb: no 'user' in msg\n"); + return; + } + + msim_msg_free(msg); + purple_debug_info("msim", "msim_get_info_cb: got for user: %s\n", username); + + user = msim_find_user(session, username); + + if (!user) { + /* User isn't on blist, create a temporary user to store info. */ + temporary_user = TRUE; + user = g_new0(MsimUser, 1); + } else { + temporary_user = FALSE; + } + + /* Update user structure with new information */ + msim_store_user_info(session, user_info_msg, user); + + user_info = purple_notify_user_info_new(); + + /* Append data from MsimUser to PurpleNotifyUserInfo for display, full */ + msim_append_user_info(session, user_info, user, TRUE); + + purple_notify_userinfo(session->gc, username, user_info, NULL, NULL); + purple_debug_info("msim", "msim_get_info_cb: username=%s\n", username); + + purple_notify_user_info_destroy(user_info); + /* TODO: do not free username, since it will be used by user_info? */ + + if (temporary_user) { + g_free(user->client_info); + g_free(user->gender); + g_free(user->location); + g_free(user->headline); + g_free(user->display_name); + g_free(user->username); + g_free(user->band_name); + g_free(user->song_name); + g_free(user->image_url); + g_free(user); + } + +} + +/** Retrieve a user's profile. + * @param username Username, user ID, or email address to lookup. + */ +void +msim_get_info(PurpleConnection *gc, const gchar *username) +{ + MsimSession *session; + MsimUser *user; + guint uid; + gchar *user_to_lookup; + MsimMessage *user_msg; + + g_return_if_fail(gc != NULL); + g_return_if_fail(username != NULL); + + session = (MsimSession *)gc->proto_data; + + g_return_if_fail(MSIM_SESSION_VALID(session)); + + /* Obtain uid of buddy. */ + user = msim_find_user(session, username); + + /* If is on buddy list, lookup by uid since it is faster. */ + if (user && (uid = purple_blist_node_get_int(&user->buddy->node, "UserID"))) { + user_to_lookup = g_strdup_printf("%d", uid); + } else { + /* Looking up buddy not on blist. Lookup by whatever user entered. */ + user_to_lookup = g_strdup(username); + } + + /* Pass the username to msim_get_info_cb(), because since we lookup + * by userid, the userinfo message will only contain the uid (not + * the username) but it would be useful to display the username too. + */ + user_msg = msim_msg_new( + "user", MSIM_TYPE_STRING, g_strdup(username), + NULL); + purple_debug_info("msim", "msim_get_info, setting up lookup, user=%s\n", username); + + msim_lookup_user(session, user_to_lookup, msim_get_info_cb, user_msg); + + g_free(user_to_lookup); +} + +/** Set your status - callback for when user manually sets it. */ +void +msim_set_status(PurpleAccount *account, PurpleStatus *status) +{ + PurpleStatusType *type; + MsimSession *session; + guint status_code; + const gchar *statstring; + + session = (MsimSession *)account->gc->proto_data; + + g_return_if_fail(MSIM_SESSION_VALID(session)); + + type = purple_status_get_type(status); + + switch (purple_status_type_get_primitive(type)) { + case PURPLE_STATUS_AVAILABLE: + purple_debug_info("msim", "msim_set_status: available (%d->%d)\n", PURPLE_STATUS_AVAILABLE, + MSIM_STATUS_CODE_ONLINE); + status_code = MSIM_STATUS_CODE_ONLINE; + break; + + case PURPLE_STATUS_INVISIBLE: + purple_debug_info("msim", "msim_set_status: invisible (%d->%d)\n", PURPLE_STATUS_INVISIBLE, + MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN); + status_code = MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN; + break; + + case PURPLE_STATUS_AWAY: + purple_debug_info("msim", "msim_set_status: away (%d->%d)\n", PURPLE_STATUS_AWAY, + MSIM_STATUS_CODE_AWAY); + status_code = MSIM_STATUS_CODE_AWAY; + break; + + default: + purple_debug_info("msim", "msim_set_status: unknown " + "status interpreting as online"); + status_code = MSIM_STATUS_CODE_ONLINE; + break; + } + + statstring = purple_status_get_attr_string(status, "message"); + + if (!statstring) { + statstring = ""; + } + + /* Status strings are plain text. */ + statstring = purple_markup_strip_html(statstring); + + msim_set_status_code(session, status_code, g_strdup(statstring)); +} + +/** Go idle. */ +void +msim_set_idle(PurpleConnection *gc, int time) +{ + MsimSession *session; + + g_return_if_fail(gc != NULL); + + session = (MsimSession *)gc->proto_data; + + g_return_if_fail(MSIM_SESSION_VALID(session)); + + if (time == 0) { + /* Going back from idle. In msim, idle is mutually exclusive + * from the other states (you can only be away or idle, but not + * both, for example), so by going non-idle I go online. + */ + /* TODO: find out how to keep old status string? */ + msim_set_status_code(session, MSIM_STATUS_CODE_ONLINE, g_strdup("")); + } else { + /* msim doesn't support idle time, so just go idle */ + msim_set_status_code(session, MSIM_STATUS_CODE_IDLE, g_strdup("")); + } +} + +/** Set status using an MSIM_STATUS_CODE_* value. + * @param status_code An MSIM_STATUS_CODE_* value. + * @param statstring Status string, must be a dynamic string (will be freed by msim_send). + */ +static void +msim_set_status_code(MsimSession *session, guint status_code, gchar *statstring) +{ + g_return_if_fail(MSIM_SESSION_VALID(session)); + g_return_if_fail(statstring != NULL); + + purple_debug_info("msim", "msim_set_status_code: going to set status to code=%d,str=%s\n", + status_code, statstring); + + if (!msim_send(session, + "status", MSIM_TYPE_INTEGER, status_code, + "sesskey", MSIM_TYPE_INTEGER, session->sesskey, + "statstring", MSIM_TYPE_STRING, statstring, + "locstring", MSIM_TYPE_STRING, g_strdup(""), + NULL)) + { + purple_debug_info("msim", "msim_set_status: failed to set status\n"); + } + +} + +/** After a uid is resolved to username, tag it with the username and submit for processing. + * + * @param session + * @param userinfo Response messsage to resolving request. + * @param data MsimMessage *, the message to attach information to. + */ +static void +msim_incoming_resolved(MsimSession *session, MsimMessage *userinfo, + gpointer data) +{ + gchar *username; + MsimMessage *msg, *body; + + g_return_if_fail(MSIM_SESSION_VALID(session)); + g_return_if_fail(userinfo != NULL); + + body = msim_msg_get_dictionary(userinfo, "body"); + g_return_if_fail(body != NULL); + + username = msim_msg_get_string(body, "UserName"); + g_return_if_fail(username != NULL); + /* Note: username will be owned by 'msg' below. */ + + msg = (MsimMessage *)data; + g_return_if_fail(msg != NULL); + + /* TODO: more elegant solution than below. attach whole message? */ + /* Special elements name beginning with '_', we'll use internally within the + * program (did not come directly from the wire). */ + msg = msim_msg_append(msg, "_username", MSIM_TYPE_STRING, username); + + /* TODO: attach more useful information, like ImageURL */ + + msim_process(session, msg); + + /* TODO: Free copy cloned from msim_preprocess_incoming(). */ + //XXX msim_msg_free(msg); + msim_msg_free(body); +} + +/* Lookup a username by userid, from buddy list. + * + * @param wanted_uid + * + * @return Username of wanted_uid, if on blist, or NULL. Static string. + * + */ +static const gchar * +msim_uid2username_from_blist(MsimSession *session, guint wanted_uid) +{ + GSList *buddies, *cur; + gchar *ret; + + buddies = purple_find_buddies(session->account, NULL); + + if (!buddies) + { + purple_debug_info("msim", "msim_uid2username_from_blist: no buddies?\n"); + return NULL; + } + + ret = NULL; + + for (cur = buddies; cur != NULL; cur = g_slist_next(cur)) + { + PurpleBuddy *buddy; + guint uid; + const gchar *name; + + /* See finch/gnthistory.c */ + buddy = cur->data; + + uid = purple_blist_node_get_int(&buddy->node, "UserID"); + name = purple_buddy_get_name(buddy); + + if (uid == wanted_uid) + { + ret = g_strdup(name); + break; + } + } + + g_slist_free(buddies); + return ret; +} + +/** Preprocess incoming messages, resolving as needed, calling msim_process() when ready to process. + * + * @param session + * @param msg MsimMessage *, freed by caller. + */ +static gboolean +msim_preprocess_incoming(MsimSession *session, MsimMessage *msg) +{ + g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); + g_return_val_if_fail(msg != NULL, FALSE); + + if (msim_msg_get(msg, "bm") && msim_msg_get(msg, "f")) { + guint uid; + const gchar *username; + + /* 'f' = userid message is from, in buddy messages */ + uid = msim_msg_get_integer(msg, "f"); + + username = msim_uid2username_from_blist(session, uid); + + if (username) { + /* Know username already, use it. */ + purple_debug_info("msim", "msim_preprocess_incoming: tagging with _username=%s\n", + username); + msg = msim_msg_append(msg, "_username", MSIM_TYPE_STRING, g_strdup(username)); + return msim_process(session, msg); + + } else { + gchar *from; + + /* Send lookup request. */ + /* XXX: where is msim_msg_get_string() freed? make _strdup and _nonstrdup. */ + purple_debug_info("msim", "msim_incoming: sending lookup, setting up callback\n"); + from = msim_msg_get_string(msg, "f"); + msim_lookup_user(session, from, msim_incoming_resolved, msim_msg_clone(msg)); + g_free(from); + + /* indeterminate */ + return TRUE; + } + } else { + /* Nothing to resolve - send directly to processing. */ + return msim_process(session, msg); + } +} + +#ifdef MSIM_USE_KEEPALIVE +/** Check if the connection is still alive, based on last communication. */ +static gboolean +msim_check_alive(gpointer data) +{ + MsimSession *session; + time_t delta; + gchar *errmsg; + + session = (MsimSession *)data; + + g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); + + delta = time(NULL) - session->last_comm; + //purple_debug_info("msim", "msim_check_alive: delta=%d\n", delta); + if (delta >= MSIM_KEEPALIVE_INTERVAL) { + errmsg = g_strdup_printf(_("Connection to server lost (no data received within %d seconds)"), (int)delta); + + purple_debug_info("msim", "msim_check_alive: %s > interval of %d, presumed dead\n",