Sat, 17 Nov 2007 02:20:01 +0000
merge of '27e2c4d9fd4b7e3538ccb882fc0ef7cc457699a4'
and '6e27033ade8131b5de7797fcf3bf717d933dd902'
--- a/.mtn-ignore Sat Nov 17 02:19:52 2007 +0000 +++ b/.mtn-ignore Sat Nov 17 02:20:01 2007 +0000 @@ -21,7 +21,7 @@ config.status config.sub configure$ -finch/finch +finch/finch$ finch/libgnt/gntmarshal.c finch/libgnt/gntmarshal.h depcomp
--- a/AUTHORS Sat Nov 17 02:19:52 2007 +0000 +++ b/AUTHORS Sat Nov 17 02:20:01 2007 +0000 @@ -17,6 +17,7 @@ Gadu-Gadu: 1511497 Daniel 'datallah' Atallah - Developer +John 'rekkanoryo' Bailey - Developer Ethan 'Paco-Paco' Blanton - Developer Thomas Butter - Developer Ka-Hing Cheung - Developer @@ -39,8 +40,10 @@ Crazy Patch Writers: ------------------- -John 'rekkanoryo' Bailey -Peter 'Bleeter' Lawler +Dennis 'EvilDennisR' Ristuccia +Peter 'Fmoo' Ruibal +Gabriel 'Nix' Schulhof +Will 'resiak' Thompson Retired Developers: ------------------ @@ -58,6 +61,7 @@ --------------------------- Felipe 'shx' Contreras Decklin Foster +Peter 'Bleeter' Lawler Robert 'Robot101' McQueen Benjamin Miller @@ -71,9 +75,9 @@ GtkSpell <http://gtkspell.sourceforge.net> responsible for the "Highlight misspelled words" feature and for gtk-nativewin <http://bunny.darktech.org/cvs/gtk-nativewin/> the default GTK+-2.0 -engine used in our win32 port. +engine originally used in our win32 port. -** LOGO DESIGNED BY: Naru Sundar ** +** ORIGINAL LOGO DESIGNED BY: Naru Sundar ** Peter Teichiman <peter@helixcode.com> Larry Ewing
--- a/COPYRIGHT Sat Nov 17 02:19:52 2007 +0000 +++ b/COPYRIGHT Sat Nov 17 02:20:01 2007 +0000 @@ -80,6 +80,7 @@ Graham Cole Jono Cole Lorenzo Colitti +Collabora Ltd. Jeff Connelly Nathan Conrad Felipe Contreras @@ -92,6 +93,7 @@ Michael Culbertson Steven Danna Chris Davies +Josh Davis Martijn Dekker Vinicius Depizzol Philip Derrin @@ -196,6 +198,7 @@ Akuke Kok Konstantin Korikov Cole Kowalski +Matt Kramer Gary Kramlich Jan Kratochvil Andrej Krivulčík
--- a/ChangeLog Sat Nov 17 02:19:52 2007 +0000 +++ b/ChangeLog Sat Nov 17 02:20:01 2007 +0000 @@ -1,15 +1,31 @@ Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul -version 2.2.2: - http://developer.pidgin.im/query?status=closed&milestone=2.2.2 - NOTE: Due to 2.2.1 being a security fix release, some bugs - marked fixed in 2.2.1 may not have been fixed until - this release (2.2.2). +version 2.3.0: + http://developer.pidgin.im/query?status=closed&milestone=2.3.0 + NOTE: Some bugs marked fixed in 2.2.1, 2.2.2 or 2.2.3 may not + have been fixed until this release (2.3.0). libpurple: * Real usernames are now shown in the system log. + * We now honor a PURPLE_DISABLE_DEPRECATED define to allow plugins to + catch deprecated functions earlier rather than later. + * Thanks to a patch from Intel, the Bonjour prpl now supports file + transfers using XEP-0096 and XEP-0065. This should enable file + transfers between libpurple clients and Gajim clients, but will not + work with iChat or Adium as they use a different file transfer + implementation. Pidgin: + * If a plugin says it can't be unloaded, we now display an error and + remove the plugin from the list of saved plugins so it won't load + at the next startup. Previously, we were ignoring this case, which + could lead to crashes. + * Mark dialog windows as transient for appropriate parent windows to + help window managers do the right thing (Gabriel Schulhof) + * Connection errors are now reported in mini-dialogs inside the buddy + list, rather than as buttons in the buddy list and with dialog + boxes. If several accounts are disabled when you sign on elsewhere, + you can now re-enable them all with a single click. * If you alias a buddy to an alias that is already present within a particular group, we now offer to merge the buddies into the same contact. @@ -31,6 +47,33 @@ * Pidgin's display is now saved with the command line for session restoration. (David Mohr) * ICQ Birthday notifications are shown as buddy list emblems. + * Plugin actions are now available from the docklet context menu + in addition to the Tool menu of the buddy list. + * The manual page has been heavily rewritten to bring it in line + with current functionality. + + Finch: + * If a plugin says it can't be unloaded, we now display an error and + remove the plugin from the list of saved plugins so it won't load + at the next startup. Previously, we were ignoring this case, which + could lead to crashes. + * It's possible to bind key-strokes to specific menuitems in the windows. + Read the 'Menus' section in the man-page for details. + * 'transpose-chars' operation for the entry boxes. The default key-binding + is ctrl+t. + * 'yank' operation for the entry boxes. The default binding is ctrl+y. + +version 2.2.2 (10/23/2007): + http://developer.pidgin.im/query?status=closed&milestone=2.2.2 + NOTE: Due to the way this release was made, it is possible that + bugs marked as fixed in 2.2.1 or 2.2.2 will not be fixed + until the next release. + + * Various bug and memory leak fixes + * Look for a default prefs.xml in the CSIDL_COMMON_APPDATA directory + (e.g. c:\Documents and Settings\All Users\ + Application Data\purple\prefs.xml) on Windows, similarly to + how this is done on other platforms. version 2.2.1 (09/29/2007): http://developer.pidgin.im/query?status=closed&milestone=2.2.1 @@ -64,7 +107,7 @@ * New protocol plugin: MySpaceIM (Jeff Connelly, Google Summer of Code) * XMPP enhancements. See - http://www.adiumx.com/blog/2007/07/soc-xmpp-update.php (Andreas + http://www.adiumx.com/blog/2007/07/soc-xmpp-update.php (Andreas Monitzer, Google Summer of Code for Adium) * Certificate management. Libpurple will validate certificates on SSL-encrypted protocols (William Ehlhardt, Google Summer of Code) @@ -2298,6 +2341,6 @@ * Add/Remove buddy from conversation window * Scroll-Wheel Mice work in Conversation Window * Fixed WindowMaker Appicon - * Version Number in About Box + * version Number in About Box * Gaim Slogan in about box :) * Created Changelog File :)
--- a/ChangeLog.API Sat Nov 17 02:19:52 2007 +0000 +++ b/ChangeLog.API Sat Nov 17 02:20:01 2007 +0000 @@ -1,6 +1,134 @@ Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul -Version 2.2.0 (09/13/2007): +version 2.3.0 (??/??/????): + libpurple: + Added: + * purple_request_field_blist_nodes_new and its accessory functions. + * a PurpleConversation field and an alias field in PurpleConvMessage + * account-authorization signals (see account-signals.dox for + details) (Stefan Ott) + * libpurple/purple.h, which includes #define's and #include's + required to compile stand-alone plugins + * PURPLE_STATUS_TUNE as a new PurpleStatusPrimitive + * purple_plugin_disable(), which is intended to be called when + a purple_plugin_unload()--which was called when a user tried + to unload a plugin--fails. This then prevents the plugin + from being saved in the saved plugins list, so it'll won't + be loaded at the next startup. + * PurpleDisconnectReason enumeration of machine-readable + types of connection error. + * purple_connection_error_reason(), to be used by prpls + (instead of purple_connection_error() and setting + gc->wants_to_die) to report errors along with a + PurpleDisconnectReason. + * PurpleConnectionUiOps.report_disconnect_reason, to be + implemented by UIs (rather than .report_disconnect) if + they want to use the reported PurpleDisconnectReason + to give a more specific error. + * A connection-error signal, fired just after the UiOp is + called with the same information. + * purple_connection_reason_is_fatal(), acting as a hint + to whether automatic reconnection should be attempted + after a connection error (rather than checking + gc->wants_to_die). + * PurpleConnectionErrorInfo, a struct to hold a + PurpleConnectionError and a const char *description. + * purple_account_get_current_error() to get the most recent + PurpleConnectionError and description (or NULL if the + account is happy with life), to allow bits of the UI to know + the last error without caching it themselves (as + PidginBuddyList does). + * purple_account_clear_current_error() to reset an account's + error state to NULL. + * An account-error-changed signal, firing when + purple_account_get_current_error()'s return value changes. + + * PidginMiniDialog, a Gtk widget-ified version of + pidgin_make_mini_dialog(). + + * purple_util_init() + * purple_util_uninit() + + * purple_network_listen_map_external() to temporarily disable + mapping ports externally via NAT-PMP or UPnP. + + Changed: + * purple_plugin_unload() now honors the return value of a + plugin's unload function and can actually return FALSE now. + * purple_plugin_unload() no longer does its own notifications + when a dependent plugin fails to unload. The UI should do + something appropriate. + + * pidgin_make_mini_dialog() now declares its return type to be + GtkWidget * rather than void *. This should not break any + existing code since any code using it must already rely on + the return type actually being GtkWidget * all along. + + Deprecated: + * pidgin_dialogs_about() + * pidgin_log_show_contact() + * pidgin_log_show() + * pidgin_plugin_dialog_show() + * pidgin_pounce_editor_show() + * pidgin_pounces_manager_show() + * pidgin_syslog_show() + + * purple_request_accept_cancel() + * purple_request_action_varg() + * purple_request_action() + * purple_request_choice_varg() + * purple_request_choice() + * purple_request_fields() + * purple_request_file() + * purple_request_folder() + * purple_request_input() + * purple_request_ok_cancel() + * purple_request_yes_no() + + * purple_connection_error() + * pidgin_blist_update_account_error_state() + * PidginBuddyList.connection_errors + + MSN: + * A new independant status type with PURPLE_STATUS_TUNE primitive, and + PURPLE_TUNE_ARTIST, PURPLE_TUNE_ALBUM and PURPLE_TUNE_TITLE + attributes. + + XMPP: + * A new independant status type with PURPLE_STATUS_TUNE primitive, and + PURPLE_TUNE_{ARTIST, TITLE, ALBUM, GENRE, COMMENT, TRACK, TIME, + YEAR, URL} attributes. + + Finch: + libgnt: + * Added gnt_color_pair, which will try to intelligenty set text + attributes in place of colors if the terminal doesn't have color + support. (Bug: #3560) All future code should use gnt_color_pair + instead of COLOR_PAIR. + * Added gnt_menuitem_set_id and gnt_menuitem_get_id to set and get the + string id of a menuitem respectively. + * Added gnt_window_get_accel_item, which returns a the id of a menuitem + bound to a keystroke. + * Added gnt_menu_get_item to get a menuitem of the given id from a + menu. + * Added gnt_menuitem_activate, which triggers the 'activate' signal on + the menuitem and calls the callback function, if available. + * Added GntEntryKillRing in GntEntry. + * Added gnt_window_set_maximize and gnt_window_get_maximize, and + GntWindowFlags enum. + +version 2.2.2 (??/??/????): + libpurple: + Changed: + * The size parameter of purple_util_write_data_to_file_absolute + has been changed to gssize instead of a size_t to correctly + indicate that -1 can be used for a nul-delimited string. + * The documentation for purple_savedstatuses_get_popular used to + incorrectly claim that the active status is excluded from the + returned list. The documentation has been corrected. Also, the + function now returns a correct list when called with a value of 0. + +version 2.2.0 (09/13/2007): libpurple: Added: * PURPLE_MESSAGE_INVISIBLE flag, which can be used by @@ -57,7 +185,7 @@ * 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): +version 2.1.1 (08/20/2007): libpurple: Changed: * PurpleAccountUiOps.request_authorize's authorize_cb and @@ -89,9 +217,6 @@ * purple_timeout_add_seconds Callers should prefer this to purple_timeout_add for timers longer than 1 second away. Be aware of the rounding, though. - * purple_timeout_add_seconds - Callers should prefer this to purple_timeout_add for timers - longer than 1 second away. Be aware of the rounding, though. * purple_xfer_get_remote_user * purple_pounces_get_all_for_ui * purple_prefs_get_children_names
--- a/ChangeLog.win32 Sat Nov 17 02:19:52 2007 +0000 +++ b/ChangeLog.win32 Sat Nov 17 02:20:01 2007 +0000 @@ -1,3 +1,23 @@ +version 2.3.0: + * Updated GTK+ to 2.12.1 (This was actually included in 2.2.2, but + didn't get into the Changelog.) + * Upgrade SILC to use the 1.1.5 toolkit + +version 2.2.2 (10/23/2007): + * Updated gtkspell to include a patch to share Aspell dictionaries + among all the input fields to avoid excessive memory usage. + * Update libxml2 to 2.6.30 + * Bonjour protocol now appears even if Bonjour for Windows isn't + present (displays message indicating Bonjour for Windows must be + installed if you try to log in and it isn't installed). + * libpurple now looks for a default prefs.xml in the COMMON_APPDATA + directory (e.g. \Documents and Settings\All Users\Application Data\purple\prefs.xml) + similarly to how this is done on other platforms. + * Updated GTK+ to 2.12.1 + +version 2.2.1 (09/29/2007): + * No changes + version 2.2.0 (09/13/2007): * Updated gtkspell to 2.0.11 * Upgrade SILC to use the 1.1.2 toolkit
--- a/Doxyfile.in Sat Nov 17 02:19:52 2007 +0000 +++ b/Doxyfile.in Sat Nov 17 02:20:01 2007 +0000 @@ -1,4 +1,4 @@ -# Doxyfile 1.4.4 +# Doxyfile 1.5.3 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project @@ -14,6 +14,14 @@ # Project related configuration options #--------------------------------------------------------------------------- +# This tag specifies the encoding used for all characters in the config file that +# follow. The default is UTF-8 which is also the encoding used for all text before +# the first occurrence of this tag. Doxygen uses libiconv (or the iconv built into +# libc) for the transcoding. See http://www.gnu.org/software/libiconv for the list of +# possible encodings. + +DOXYFILE_ENCODING = UTF-8 + # The PROJECT_NAME tag is a single word (or a sequence of words surrounded # by quotes) that should identify the project. @@ -45,23 +53,14 @@ # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # The default language is English, other supported languages are: -# Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, -# Finnish, French, German, Greek, Hungarian, Italian, Japanese, Japanese-en -# (Japanese with English messages), Korean, Norwegian, Polish, Portuguese, -# Romanian, Russian, Serbian, Slovak, Slovene, Spanish, Swedish, and Ukrainian. +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Finnish, French, German, Greek, Hungarian, +# Italian, Japanese, Japanese-en (Japanese with English messages), Korean, +# Korean-en, Lithuanian, Norwegian, Polish, Portuguese, Romanian, Russian, +# Serbian, Slovak, Slovene, Spanish, Swedish, and Ukrainian. OUTPUT_LANGUAGE = English -# This tag can be used to specify the encoding used in the generated output. -# The encoding is not always determined by the language that is chosen, -# but also whether or not the output is meant for Windows or non-Windows users. -# In case there is a difference, setting the USE_WINDOWS_ENCODING tag to YES -# forces the Windows encoding (this is the default for the Windows binary), -# whereas setting the tag to NO uses a Unix-style encoding (the default for -# all platforms other than Windows). - -USE_WINDOWS_ENCODING = NO - # If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will # include brief member descriptions after the members that are listed in # the file and class documentation (similar to JavaDoc). @@ -76,16 +75,28 @@ REPEAT_BRIEF = YES +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # Doxygen will generate a detailed section even if there is only a brief # description. ALWAYS_DETAILED_SEC = NO -# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all inherited -# members of a class in the documentation of that class as if those members were -# ordinary class members. Constructors, destructors and assignment operators of -# the base classes will not be shown. +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. INLINE_INHERITED_MEMB = NO @@ -98,10 +109,21 @@ # If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag # can be used to strip a user-defined part of the path. Stripping is # only done if one of the specified strings matches the left-hand part of -# the path. It is allowed to use relative paths in the argument list. +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. STRIP_FROM_PATH = +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter # (but less readable) file names. This can be useful is your file systems # doesn't support long names like on DOS, Mac, or CD-ROM. @@ -111,11 +133,19 @@ # If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen # will interpret the first line (until the first dot) of a JavaDoc-style # comment as the brief description. If set to NO, the JavaDoc -# comments will behave just like the Qt-style comments (thus requiring an -# explict @brief command for a brief description. +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) JAVADOC_AUTOBRIEF = YES +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen # treat a multi-line C++ special comment block (i.e. a block of //! or /// # comments) as a brief description. This used to be the default behaviour. @@ -133,17 +163,10 @@ # If the INHERIT_DOCS tag is set to YES (the default) then an undocumented # member inherits the documentation from any documented member that it -# reimplements. +# re-implements. INHERIT_DOCS = YES -# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC -# tag is set to YES, then doxygen will reuse the documentation of the first -# member in the group (if any) for the other members of the group. By default -# all members of a group must be documented explicitly. - -DISTRIBUTE_GROUP_DOC = NO - # If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce # a new page for each member. If set to NO, the documentation of a member will # be part of the file/class/namespace that contains it. @@ -162,29 +185,51 @@ # will result in a user-defined paragraph with heading "Side Effects:". # You can put \n's in the value part of an alias to insert newlines. -ALIASES = "signal=- @ref" \ - "signaldef=@section" \ - "endsignaldef=" \ - "signalproto=@code" \ - "endsignalproto=@endcode" \ - "signaldesc=@par Description:" \ - "signals=@b Signals:" \ - "endsignals=" +ALIASES = "signal=- @ref " \ + "signaldef=@section " \ + "endsignaldef= " \ + "signalproto=@code " \ + "endsignalproto=@endcode " \ + "signaldesc=@par Description: " \ + "signals=@b Signals: " \ + "endsignals= " \ + "constreturn=@note The return value of this function must not be modified or freed. @return " -# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources -# only. Doxygen will then generate output that is more tailored for C. +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. # For instance, some of the names that are used will be different. The list # of all members will be omitted, etc. OPTIMIZE_OUTPUT_FOR_C = YES -# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java sources -# only. Doxygen will then generate output that is more tailored for Java. +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for Java. # For instance, namespaces will be presented as packages, qualified scopes # will look different, etc. OPTIMIZE_OUTPUT_JAVA = NO +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want to +# include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + # Set the SUBGROUPING tag to YES (the default) to allow class member groups of # the same type (for instance a group of public functions) to be put as a # subgroup of that type (e.g. under the Public Functions section). Set it to @@ -227,6 +272,13 @@ EXTRACT_LOCAL_METHODS = YES +# If this flag is set to YES, the members of anonymous namespaces will be extracted +# and appear in the documentation as a namespace called 'anonymous_namespace{file}', +# where file will be replaced with the base name of the file that contains the anonymous +# namespace. By default anonymous namespace are hidden. + +EXTRACT_ANON_NSPACES = NO + # If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members of documented classes, files or namespaces. # If set to NO (the default) these members will be included in the @@ -267,7 +319,7 @@ # file names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows -# users are advised to set this option to NO. +# and Mac users are advised to set this option to NO. CASE_SENSE_NAMES = YES @@ -359,7 +411,7 @@ # If the sources in your project are distributed over multiple directories # then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy -# in the documentation. The default is YES. +# in the documentation. The default is NO. SHOW_DIRECTORIES = YES @@ -368,7 +420,7 @@ # version control system). Doxygen will invoke the program by executing (via # popen()) the command <command> <input-file>, where <command> is the value of # the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file -# provided by doxygen. Whatever the progam writes to standard output +# provided by doxygen. Whatever the program writes to standard output # is used as the file version. See the manual for examples. FILE_VERSION_FILTER = @@ -412,9 +464,11 @@ # The WARN_FORMAT tag determines the format of the warning messages that # doxygen can produce. The string should contain the $file, $line, and $text # tags, which will be replaced by the file and line number from which the -# warning originated and the warning text. +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) -WARN_FORMAT = "$file:$line: $text" +WARN_FORMAT = "$file:$line: $text " # The WARN_LOGFILE tag can be used to specify a file to which warning # and error messages should be written. If left blank the output is written @@ -432,17 +486,24 @@ # with spaces. INPUT = libpurple \ - finch \ - finch/libgnt \ - pidgin \ + finch \ + finch/libgnt \ + pidgin \ doc +# This tag can be used to specify the character encoding of the source files that +# doxygen parses. Internally doxygen uses the UTF-8 encoding, which is also the default +# input encoding. Doxygen uses libiconv (or the iconv built into libc) for the transcoding. +# See http://www.gnu.org/software/libiconv for the list of possible encodings. + +INPUT_ENCODING = UTF-8 + # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank the following patterns are tested: -# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx *.hpp -# *.h++ *.idl *.odl *.cs *.php *.php3 *.inc +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx +# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py FILE_PATTERNS = *.h \ *.dox @@ -457,19 +518,30 @@ # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. -EXCLUDE = +EXCLUDE = libpurple/purple-client.h \ + libpurple/purple-client-bindings.h -# The EXCLUDE_SYMLINKS tag can be used select whether or not files or directories -# that are symbolic links (a Unix filesystem feature) are excluded from the input. +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are excluded +# from the input. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude -# certain files from those directories. +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* EXCLUDE_PATTERNS = +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the output. +# The symbol name can be a fully qualified name, a word, or if the wildcard * is used, +# a substring. Examples: ANamespace, AClass, AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + # The EXAMPLE_PATH tag can be used to specify one or more files or # directories that contain example code fragments that are included (see # the \include command). @@ -501,7 +573,8 @@ # by executing (via popen()) the command <filter> <input-file>, where <filter> # is the value of the INPUT_FILTER tag, and <input-file> is the name of an # input file. Doxygen will then use the output that the filter program writes -# to standard output. +# to standard output. If FILTER_PATTERNS is specified, this tag will be +# ignored. INPUT_FILTER = @@ -525,9 +598,13 @@ #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will -# be generated. Documented entities will be cross-referenced with these sources. +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. If you have enabled CALL_GRAPH or CALLER_GRAPH +# then you must also enable this option. If you don't then doxygen will produce +# a warning and turn it on anyway -SOURCE_BROWSER = NO +SOURCE_BROWSER = YES # Setting the INLINE_SOURCES tag to YES will include the body # of functions and classes directly in the documentation. @@ -552,6 +629,13 @@ REFERENCES_RELATION = YES +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. Otherwise they will link to the documentstion. + +REFERENCES_LINK_SOURCE = YES + # If the USE_HTAGS tag is set to YES then the references to source code # will point to the HTML generated by the htags(1) tool instead of doxygen # built-in source browser. The htags tool is part of GNU's global source @@ -627,7 +711,9 @@ # The HTML_STYLESHEET tag can be used to specify a user-defined cascading # style sheet that is used by each HTML page. It can be used to # fine-tune the look of the HTML output. If the tag is left blank doxygen -# will generate a default style sheet +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! HTML_STYLESHEET = @@ -644,10 +730,18 @@ GENERATE_HTMLHELP = YES +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = NO + # If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can # be used to specify the file name of the resulting .chm file. You # can add a path in front of the file if the result should not be -# written to the html output dir. +# written to the html output directory. CHM_FILE = @@ -782,7 +876,7 @@ #--------------------------------------------------------------------------- # If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output -# The RTF output is optimised for Word 97 and may not look very pretty with +# The RTF output is optimized for Word 97 and may not look very pretty with # other RTF readers or editors. GENERATE_RTF = NO @@ -809,7 +903,7 @@ RTF_HYPERLINKS = NO # Load stylesheet definitions from file. Syntax is similar to doxygen's -# config file, i.e. a series of assigments. You only have to provide +# config file, i.e. a series of assignments. You only have to provide # replacements, missing definitions are set to their default value. RTF_STYLESHEET_FILE = @@ -853,11 +947,9 @@ # If the GENERATE_XML tag is set to YES Doxygen will # generate an XML file that captures the structure of -# the code including all documentation. Note that this -# feature is still experimental and incomplete at the -# moment. +# the code including all documentation. -GENERATE_XML = NO +GENERATE_XML = YES # The XML_OUTPUT tag is used to specify where the XML pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be @@ -877,6 +969,13 @@ XML_DTD = +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- @@ -941,7 +1040,7 @@ # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES # then the macro expansion is limited to the macros specified with the -# PREDEFINED and EXPAND_AS_PREDEFINED tags. +# PREDEFINED and EXPAND_AS_DEFINED tags. EXPAND_ONLY_PREDEF = NO @@ -967,7 +1066,9 @@ # are defined before the preprocessor is started (similar to the -D option of # gcc). The argument of the tag is a list of macros of the form: name # or name=definition (no spaces). If the definition and the = are -# omitted =1 is assumed. +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. PREDEFINED = @@ -981,13 +1082,13 @@ # If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then # doxygen's preprocessor will remove all function-like macros that are alone # on a line, have an all uppercase name, and do not end with a semicolon. Such -# function macros are typically used for boiler-plate code, and will confuse the -# parser if not removed. +# function macros are typically used for boiler-plate code, and will confuse +# the parser if not removed. SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- -# Configuration::addtions related to external references +# Configuration::additions related to external references #--------------------------------------------------------------------------- # The TAGFILES option can be used to specify one or more tagfiles. @@ -1034,13 +1135,22 @@ #--------------------------------------------------------------------------- # If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will -# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base or -# super classes. Setting the tag to NO turns the diagrams off. Note that this -# option is superceded by the HAVE_DOT option below. This is only a fallback. It is -# recommended to install and use dot, since it yields more powerful graphs. +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option is superseded by the HAVE_DOT option below. This is only a +# fallback. It is recommended to install and use dot, since it yields more +# powerful graphs. CLASS_DIAGRAMS = YES +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see http://www.mcternan.me.uk/mscgen/) to +# produce the chart and insert it in the documentation. The MSCGEN_PATH tag allows you to +# specify the directory where the mscgen tool resides. If left empty the tool is assumed to +# be found in the default search path. + +MSCGEN_PATH = + # If set to YES, the inheritance and collaboration graphs will hide # inheritance and usage relations if the target is undocumented # or is not a class. @@ -1074,7 +1184,7 @@ GROUP_GRAPHS = YES # If the UML_LOOK tag is set to YES doxygen will generate inheritance and -# collaboration diagrams in a style similiar to the OMG's Unified Modeling +# collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. UML_LOOK = YES @@ -1098,7 +1208,7 @@ INCLUDED_BY_GRAPH = YES -# If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will +# If the CALL_GRAPH, SOURCE_BROWSER and HAVE_DOT tags are set to YES then doxygen will # generate a call dependency graph for every global function or class method. # Note that enabling this option will significantly increase the time of a run. # So in most cases it will be better to enable call graphs for selected @@ -1106,6 +1216,14 @@ CALL_GRAPH = YES +# If the CALLER_GRAPH, SOURCE_BROWSER and HAVE_DOT tags are set to YES then doxygen will +# generate a caller dependency graph for every global function or class method. +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable caller graphs for selected +# functions only using the \callergraph command. + +CALLER_GRAPH = NO + # If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen # will graphical hierarchy of all classes instead of a textual one. @@ -1125,7 +1243,7 @@ DOT_IMAGE_FORMAT = png # The tag DOT_PATH can be used to specify the path where the dot tool can be -# found. If left blank, it is assumed the dot tool can be found on the path. +# found. If left blank, it is assumed the dot tool can be found in the path. DOT_PATH = @@ -1135,32 +1253,25 @@ DOTFILE_DIRS = -# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width -# (in pixels) of the graphs generated by dot. If a graph becomes larger than -# this value, doxygen will try to truncate the graph, so that it fits within -# the specified constraint. Beware that most browsers cannot cope with very -# large images. - -MAX_DOT_GRAPH_WIDTH = 1024 +# The MAX_DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the number +# of direct children of the root node in a graph is already larger than +# MAX_DOT_GRAPH_NOTES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. -# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height -# (in pixels) of the graphs generated by dot. If a graph becomes larger than -# this value, doxygen will try to truncate the graph, so that it fits within -# the specified constraint. Beware that most browsers cannot cope with very -# large images. - -MAX_DOT_GRAPH_HEIGHT = 1024 +DOT_GRAPH_MAX_NODES = 50 # The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the # graphs generated by dot. A depth value of 3 means that only nodes reachable -# from the root by following a path via at most 3 edges will be shown. Nodes that -# lay further from the root node will be omitted. Note that setting this option to -# 1 or 2 may greatly reduce the computation time needed for large code bases. Also -# note that a graph may be further truncated if the graph's image dimensions are -# not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH and MAX_DOT_GRAPH_HEIGHT). -# If 0 is used for the depth value (the default), the graph is not depth-constrained. +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. -MAX_DOT_GRAPH_DEPTH = 0 +MAX_DOT_GRAPH_DEPTH = 2 # Set the DOT_TRANSPARENT tag to YES to generate images with a transparent # background. This is disabled by default, which results in a white background. @@ -1190,7 +1301,7 @@ DOT_CLEANUP = YES #--------------------------------------------------------------------------- -# Configuration::addtions related to the search engine +# Configuration::additions related to the search engine #--------------------------------------------------------------------------- # The SEARCHENGINE tag specifies whether or not a search engine should be
--- a/INSTALL Sat Nov 17 02:19:52 2007 +0000 +++ b/INSTALL Sat Nov 17 02:20:01 2007 +0000 @@ -32,15 +32,19 @@ `configure' itself. Running `configure' takes awhile. While running, it prints some - messages telling which features it is checking for. + messages telling which features it is checking for. If it finishes + successfully, it will print out of a summary of the build options. + This summary will also appear in the help->about dialog. - 2. Type `make' to compile the package. + 2. Type `make' to compile the package. On some systems, you may need + to use `gmake' instead. 3. Optionally, type `make check' to run any self-tests that come with the package. 4. Type `make install' to install the programs and any data files and - documentation. + documentation. You must have write access to the prefix you are + installing to. See below for more details on the prefix. 5. You can remove the program binaries and object files from the source code directory by typing `make clean'. To also remove the @@ -50,6 +54,9 @@ for the package's developers. If you use it, you may have to get all sorts of other programs in order to regenerate files that came with the distribution. + + 6. If you have *not* run make distclean, you can use the target + `make uninstall` to remove the files installed by `make install`. Compilers and Options ===================== @@ -119,6 +126,40 @@ you can use the `configure' options `--x-includes=DIR' and `--x-libraries=DIR' to specify their locations. + By default both the GTK+ UI (Pidgin) and the ncurses UI (Finch) will be +built, assuming that configure finds the necessary libraries and headers for +each. You can disable the GTK+ UI with `--disable-gtkui' and the ncurses UI +with `--disable-consoleui'. + + `--disable-screensaver' will build libpurple without support for detecting +when it should mark accounts idle based on mouse or keyboard usage. + + `--disable-sm' will build without support for the X session management. +Doing so will remove the ability to have pidgin start up with your window +manager. + + `--disable-gtkspell' will remove the ability to highlight misspelled words. + + `--disable-gevolution' will cause the evolution integration plugin not to +compile. + + `--disable-gstreamer' will build without sound support. This applies to +*both* Pidgin and Finch. + + `--enable-gnutls=yes,no' will enable or disable the use of gnutls for ssl support. Disabling both gnutls and nss will mean you cannot use either MSN or Google Talk. There is no static option for gnutls at this time. + + `--enable-nss=yes,no,static' will enable or disable the use of nss for ssl support. This is the only option for ssl support if you are attempting to compile a static version of Pidgin or Finch. + +Optional Packages: + + `--with-silc-includes=DIR' and `--with-silc-libs=DIR' can be used if your silc libraries are installed to a location not in your path. + + `--with-static-prpls' takes a list of comma separated protocols to build in statically (rather than as plugins). Use this with care. + + `--with-dynamic-prpls' takes a list of comma separated protocols also. If used only those listed will be built. If no protocols are listed with either `--with-static-prpls' or with `--with-dynamic-prpls' then Pidgin and Finch will be effectively useless. + + If configure does not find python, it will build without DBUS support. Thiswill disable scripts such as purple-remote and purple-uri-handler, effectively disabling integration with the browser. You can tell configure where your python binary is located with `--with-python=PATH' + Specifying the System Type ========================== @@ -179,4 +220,4 @@ Print the version of Autoconf used to generate the `configure' script, and exit. -`configure' also accepts some other, not widely useful, options. +`configure' also accepts some other, not widely useful, options. Many of these are related to having various optional libaries installed to locations outside of your path.
--- a/Makefile.am Sat Nov 17 02:19:52 2007 +0000 +++ b/Makefile.am Sat Nov 17 02:20:01 2007 +0000 @@ -42,12 +42,19 @@ GNT_DIR=finch endif -SUBDIRS = libpurple doc $(GNT_DIR) $(GTK_DIR) m4macros po share +SUBDIRS = libpurple doc $(GNT_DIR) $(GTK_DIR) m4macros po share/ca-certs share/sounds docs: Doxyfile if HAVE_DOXYGEN @echo "Running doxygen..." @doxygen +if HAVE_XSLTPROC + @echo "Generating devhelp index..." + @xsltproc doxy2devhelp.xsl doc/xml/index.xml > doc/html/pidgin.devhelp + @echo "(Symlink doc/html to ~/.local/share/gtk-doc/html/pidgin to make devhelp see the documentation)" +else + @echo "Not generating devhelp index: xsltproc was not found by configure" +endif else @echo "doxygen was not found during configure. Aborting." @echo;
--- a/Makefile.mingw Sat Nov 17 02:19:52 2007 +0000 +++ b/Makefile.mingw Sat Nov 17 02:20:01 2007 +0000 @@ -36,22 +36,36 @@ echo $$gtk_version \ ) +STRIPPED_RELEASE_DIR = $(PIDGIN_TREE_TOP)/pidgin-$(PIDGIN_VERSION)-win32bin + + # Any *.dll or *.exe files included in win32-install-dir that we don't compile # should be included in this list so they don't get stripped EXTERNAL_DLLS = \ + comerr32.dll \ freebl3.dll \ + gssapi32.dll \ + k5sprt32.dll \ + krb5_32.dll \ libgtkspell.dll \ libmeanwhile-1.dll \ + libsasl.dll \ libxml2.dll \ nspr4.dll \ nss3.dll \ nssckbi.dll \ plc4.dll \ plds4.dll \ - silc.dll \ - silcclient.dll \ + saslANONYMOUS.dll \ + saslCRAMMD5.dll \ + saslDIGESTMD5.dll \ + saslGSSAPI.dll \ + saslLOGIN.dll \ + saslPLAIN.dll \ + libsilc-1-1-2.dll \ + libsilcclient-1-1-2.dll \ + smime3.dll \ softokn3.dll \ - smime3.dll \ ssl3.dll #build an expression for `find` to use to ignore the above files @@ -62,33 +76,42 @@ all: $(PIDGIN_CONFIG_H) $(MAKE) -C $(PURPLE_TOP) -f $(MINGW_MAKEFILE) $(MAKE) -C $(PIDGIN_TOP) -f $(MINGW_MAKEFILE) +ifndef DISABLE_NLS $(MAKE) -C $(PURPLE_PO_TOP) -f $(MINGW_MAKEFILE) +endif install: all $(PIDGIN_INSTALL_DIR) $(MAKE) -C $(PURPLE_TOP) -f $(MINGW_MAKEFILE) install $(MAKE) -C $(PIDGIN_TOP) -f $(MINGW_MAKEFILE) install +ifndef DISABLE_NLS $(MAKE) -C $(PURPLE_PO_TOP) -f $(MINGW_MAKEFILE) install - $(MAKE) -C share -f $(MINGW_MAKEFILE) install +endif + $(MAKE) -C share/ca-certs -f $(MINGW_MAKEFILE) install + $(MAKE) -C share/sounds -f $(MINGW_MAKEFILE) install create_release_install_dir: install - rm -rf $(PIDGIN_INSTALL_DIR).release - cp -R $(PIDGIN_INSTALL_DIR) $(PIDGIN_INSTALL_DIR).release - find $(PIDGIN_INSTALL_DIR).release \( -name '*.dll' -o -name '*.exe' \) \ + rm -rf $(STRIPPED_RELEASE_DIR) + cp -R $(PIDGIN_INSTALL_DIR) $(STRIPPED_RELEASE_DIR) + find $(STRIPPED_RELEASE_DIR) \( -name '*.dll' -o -name '*.exe' \) \ -not \( -false $(EXTERNAL_DLLS_FIND_EXP) \) -exec $(STRIP) --strip-unneeded {} ';' installer: create_release_install_dir - $(MAKENSIS) /V3 /DPIDGIN_VERSION="$(PIDGIN_VERSION)" /DPIDGIN_PRODUCT_VERSION="$(PIDGIN_PRODUCT_VERSION)" /DWITH_GTK /DPIDGIN_INSTALL_DIR="$(PIDGIN_INSTALL_DIR).release" /DGTK_INSTALL_VERSION="$(GTK_INSTALL_VERSION)" pidgin/win32/nsis/pidgin-installer.nsi + $(MAKENSIS) /V3 /DPIDGIN_VERSION="$(PIDGIN_VERSION)" /DPIDGIN_PRODUCT_VERSION="$(PIDGIN_PRODUCT_VERSION)" /DWITH_GTK /DPIDGIN_INSTALL_DIR="$(STRIPPED_RELEASE_DIR)" /DGTK_INSTALL_VERSION="$(GTK_INSTALL_VERSION)" pidgin/win32/nsis/pidgin-installer.nsi mv pidgin/win32/nsis/pidgin*.exe ./ installer_nogtk: create_release_install_dir - $(MAKENSIS) /V3 /DPIDGIN_VERSION="$(PIDGIN_VERSION)" /DPIDGIN_PRODUCT_VERSION="$(PIDGIN_PRODUCT_VERSION)" /DPIDGIN_INSTALL_DIR="$(PIDGIN_INSTALL_DIR).release" /DGTK_INSTALL_VERSION="$(GTK_INSTALL_VERSION)" pidgin/win32/nsis/pidgin-installer.nsi + $(MAKENSIS) /V3 /DPIDGIN_VERSION="$(PIDGIN_VERSION)" /DPIDGIN_PRODUCT_VERSION="$(PIDGIN_PRODUCT_VERSION)" /DPIDGIN_INSTALL_DIR="$(STRIPPED_RELEASE_DIR)" /DGTK_INSTALL_VERSION="$(GTK_INSTALL_VERSION)" pidgin/win32/nsis/pidgin-installer.nsi mv pidgin/win32/nsis/pidgin*.exe ./ installer_debug: install $(MAKENSIS) /V3 /DPIDGIN_VERSION="$(PIDGIN_VERSION)" /DPIDGIN_PRODUCT_VERSION="$(PIDGIN_PRODUCT_VERSION)" /DPIDGIN_INSTALL_DIR="$(PIDGIN_INSTALL_DIR)" /DDEBUG /DGTK_INSTALL_VERSION="$(GTK_INSTALL_VERSION)" pidgin/win32/nsis/pidgin-installer.nsi mv pidgin/win32/nsis/pidgin*.exe ./ -installers: installer installer_nogtk installer_debug +installer_zip: create_release_install_dir + rm -f pidgin-$(PIDGIN_VERSION)-win32-bin.zip + zip -9 -r pidgin-$(PIDGIN_VERSION)-win32-bin.zip $(STRIPPED_RELEASE_DIR) + +installers: installer installer_nogtk installer_debug installer_zip Doxyfile.mingw: Doxyfile.in sed -e "s/@PACKAGE@/pidgin/" -e "s/@VERSION@/$(PIDGIN_VERSION)/" -e "s/@top_srcdir@/$(PIDGIN_TREE_TOP)/g" -e "s/@enable_dot@/NO/" Doxyfile.in > Doxyfile.mingw
--- a/NEWS Sat Nov 17 02:19:52 2007 +0000 +++ b/NEWS Sat Nov 17 02:20:01 2007 +0000 @@ -1,5 +1,11 @@ Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul +2.2.2 (10/23/2007): + Luke: Because the main branch of pidgin development is still not + ready for public consumption, I have taken some time to try to + pull the many bug fixes that have happened since then into a + separate branch. This release is the result of that effort. + 2.2.1 (9/28/2007): Richard: We have some new code in the pipeline, but it's not quite ready for a general release. Instead, this is basically a bug fix
--- a/config.h.mingw Sat Nov 17 02:19:52 2007 +0000 +++ b/config.h.mingw Sat Nov 17 02:20:01 2007 +0000 @@ -377,7 +377,8 @@ /* #define USE_SM 1 */ /* Version number of package */ -/* #define VERSION "2.0.0dev" */ +#define VERSION "@VERSION@" +#define DISPLAY_VERSION "@DISPLAY_VERSION@" /* Define to 1 if your processor stores words with the most significant byte first (like Motorola and SPARC, unlike Intel and VAX). */ @@ -409,3 +410,6 @@ */ #define HAVE_VSNPRINTF 1 +#define HAVE_FILENO 1 + +
--- a/configure.ac Sat Nov 17 02:19:52 2007 +0000 +++ b/configure.ac Sat Nov 17 02:20:01 2007 +0000 @@ -43,10 +43,10 @@ # # Make sure to update finch/libgnt/configure.ac with libgnt version changes. # -m4_define([purple_lt_current], [2]) +m4_define([purple_lt_current], [3]) m4_define([purple_major_version], [2]) -m4_define([purple_minor_version], [2]) -m4_define([purple_micro_version], [2]) +m4_define([purple_minor_version], [3]) +m4_define([purple_micro_version], [0]) m4_define([purple_version_suffix], []) m4_define([purple_version], [purple_major_version.purple_minor_version.purple_micro_version]) @@ -55,7 +55,7 @@ m4_define([gnt_lt_current], [2]) m4_define([gnt_major_version], [2]) m4_define([gnt_minor_version], [2]) -m4_define([gnt_micro_version], [2]) +m4_define([gnt_micro_version], [3]) m4_define([gnt_version_suffix], []) m4_define([gnt_version], [gnt_major_version.gnt_minor_version.gnt_micro_version]) @@ -136,19 +136,27 @@ ;; esac -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 lo lt mk my_MM nb ne nl nn pa pl pt_BR pt ps ro ru sk sl sq sr sr@latin 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 lo lt mk my_MM nb ne nl nn pa pl pt_BR pt ps ro ru sk sl sq sr sr@latin sv ta te th tr uk ur 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 dnl AM_GLIB_GNU_GETTEXT found it. -if test x$MSGFMT = xno +if test x$MSGFMT = xno -o x$MSGFMT$GMSGFMT = x then AC_ERROR([ The msgfmt command is required to build libpurple. If it is installed on your system, ensure that it is in your path. If it is not, install GNU gettext to continue. + +If you have msgfmt installed, but for some reason this error message +is still displayed, you have encountered what appears to be a bug in +third-party configure macros. Try setting the MSGFMT environment +variable to the absolute path to your msgfmt binary and trying +configure again, like this: + +MSGFMT=/path/to/msgfmt ./configure ... ]) fi @@ -225,6 +233,30 @@ dnl FreeBSD doesn't have libdl, dlopen is provided by libc AC_CHECK_FUNC(dlopen, LIBDL="", [AC_CHECK_LIB(dl, dlopen, LIBDL="-ldl")]) +AC_MSG_CHECKING(for fileno()) +AC_TRY_RUN([ +#include <stdio.h> + +int main(int argc, char *argv[]) +{ + int fd; + + fd = fileno(stdout); + + return !(fd > 0); +} +], [ + AC_MSG_RESULT(yes) + AC_DEFINE([HAVE_FILENO], [1], + [Define to 1 if your stdio has int fileno(FILE *).]) +], [ + AC_MSG_RESULT(no) +], [ + # Fallback for Cross Compiling... + # This will enable the compatibility code. + AC_MSG_RESULT(no) +]) + AC_MSG_CHECKING(for the %z format string in strftime()) AC_TRY_RUN([ #ifdef HAVE_SYS_TIME_H @@ -275,6 +307,17 @@ AC_SUBST(GLIB_CFLAGS) AC_SUBST(GLIB_LIBS) +AC_ARG_WITH([extraversion], + AC_HELP_STRING([--with-extraversion=STRING], + [extra version number to be displayed in Help->About and --help (for packagers)]), + EXTRA_VERSION=$withval) + +if test x"$EXTRA_VERSION" != "x" ; then + AC_DEFINE_UNQUOTED(DISPLAY_VERSION, "$VERSION-$EXTRA_VERSION", [display version info]) +else + AC_DEFINE_UNQUOTED(DISPLAY_VERSION, "$VERSION", [display version info]) +fi + AC_ARG_WITH(x, [], with_x="$withval", with_x="yes") AC_ARG_ENABLE(gtkui, [AC_HELP_STRING([--disable-gtkui], @@ -857,7 +900,7 @@ CPPFLAGS_save="$CPPFLAGS" CPPFLAGS="$CPPFLAGS $GADU_CFLAGS" AC_TRY_COMPILE([#include <libgadu.h>], [ -#ifdef __GG_LIBGADU_HAVE_OPENSSL +#if defined(__GG_LIBGADU_HAVE_OPENSSL) || defined(GG_CONFIG_HAVE_OPENSSL) #error "libgadu is not compatible with the GPL when compiled with OpenSSL support." #endif ], [ @@ -869,10 +912,11 @@ echo echo echo "libgadu is not compatible with the GPL when compiled with OpenSSL support." - echo "Please recompile libgadu using:" + echo "To compile against system libgadu, please recompile libgadu using:" echo "./autogen.sh --disable-libgadu-openssl --disable-static --enable-shared" echo "Then rerun this ./configure" echo + echo "Falling back to using our own copy of libgadu" echo GADU_LIBS="" GADU_CFLAGS="" @@ -886,6 +930,9 @@ AC_SUBST(GADU_LIBS) AC_SUBST(GADU_CFLAGS) +# uncomment the next line to make MSNP14 the available +# AC_ARG_ENABLE(msnp14,[AC_HELP_STRING([--enable-msnp14], [Disable the newer MSNP14 protocol])],,enable_msnp14=no) +enable_msnp14=no AC_ARG_ENABLE(distrib,,,enable_distrib=no) AM_CONDITIONAL(DISTRIB, test "x$enable_distrib" = "xyes") @@ -906,6 +953,9 @@ STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/bonjour//'` fi fi +if test "x$enable_msnp14" != "xyes" ; then + STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/msn/msnp9/'` +fi if test "x$silcincludes" != "xyes" -o "x$silcclient" != "xyes"; then STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/silc/silc10/'` fi @@ -930,6 +980,8 @@ STATIC_LINK_LIBS="$STATIC_LINK_LIBS \$(top_builddir)/libpurple/protocols/$i/lib${i}purple.a" elif test "x$i" = "xsilc10"; then STATIC_LINK_LIBS="$STATIC_LINK_LIBS \$(top_builddir)/libpurple/protocols/$i/libsilcpurple.a" + elif test "x$i" = "xmsnp9"; then + STATIC_LINK_LIBS="$STATIC_LINK_LIBS \$(top_builddir)/libpurple/protocols/$i/libmsn.a" else STATIC_LINK_LIBS="$STATIC_LINK_LIBS \$(top_builddir)/libpurple/protocols/$i/lib$i.a" fi @@ -942,6 +994,7 @@ irc) static_irc=yes ;; jabber) static_jabber=yes ;; msn) static_msn=yes ;; + msnp9) static_msn=yes ;; myspace) static_myspace=yes ;; novell) static_novell=yes ;; oscar) static_oscar=yes ;; @@ -989,6 +1042,9 @@ DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/bonjour//'` fi fi +if test "x$enable_msnp14" != "xyes" ; then + DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/msn/msnp9/'` +fi if test "x$silcincludes" != "xyes" -o "x$silcclient" != "xyes"; then DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/silc/silc10/'` fi @@ -1003,6 +1059,7 @@ irc) dynamic_irc=yes ;; jabber) dynamic_jabber=yes ;; msn) dynamic_msn=yes ;; + msnp9) dynamic_msn=yes ;; myspace) dynamic_myspace=yes ;; novell) dynamic_novell=yes ;; oscar) dynamic_oscar=yes ;; @@ -1960,9 +2017,12 @@ if test "x$enable_plugins" = "xyes" ; then AC_DEFINE(PURPLE_PLUGINS, 1, [Define if plugins are enabled.]) AM_CONDITIONAL(PLUGINS, true) + PLUGINS_DEFINE="#define PURPLE_PLUGINS 1" else AM_CONDITIONAL(PLUGINS, false) + PLUGINS_DEFINE="#undef PURPLE_PLUGINS" fi +AC_SUBST(PLUGINS_DEFINE) dnl ####################################################################### dnl # Check for Cyrus-SASL (for Jabber) @@ -2093,6 +2153,10 @@ [AC_HELP_STRING([--enable-dot], [enable graphs in doxygen via 'dot'])], enable_dot="$enableval", enable_dot="yes") +AC_ARG_ENABLE(devhelp, + [AC_HELP_STRING([--enable-devhelp], + [enable building index for devhelp documentation browser])], + enable_devhelp="$enableval", enable_devhelp="yes") if test "x$enable_doxygen" = xyes; then AC_CHECK_PROG(DOXYGEN, doxygen, true, false) @@ -2112,14 +2176,28 @@ AC_DEFINE_UNQUOTED(HAVE_DOT, 1, [whether or not we have dot]) fi fi + + if test "x$enable_devhelp" = "xyes"; then + AC_CHECK_PROG(XSLTPROC, xsltproc, true, false) + + if test $XSLTPROC = false; then + enable_devhelp="no"; + AC_MSG_WARN([*** xsltproc not found; devhelp index will not be created]) + else + AC_DEFINE_UNQUOTED(HAVE_XSLTPROC, 1, [whether or not we have xsltproc for devhelp index]) + fi + fi fi else enable_dot="no" + enable_devhelp="no" fi AC_SUBST(enable_doxygen) AC_SUBST(enable_dot) +AC_SUBST(enable_devhelp) AM_CONDITIONAL(HAVE_DOXYGEN, test "x$enable_doxygen" = "xyes") +AM_CONDITIONAL(HAVE_XSLTPROC, test "x$enable_devhelp" = "xyes") AC_ARG_ENABLE(debug, [AC_HELP_STRING([--enable-debug], [compile with debugging support])], , enable_debug=no) @@ -2141,68 +2219,9 @@ pidgin/pidgin.pc pidgin/pidgin-uninstalled.pc pidgin/pixmaps/Makefile - pidgin/pixmaps/animations/Makefile - pidgin/pixmaps/animations/16/Makefile - pidgin/pixmaps/buddy_icons/Makefile pidgin/pixmaps/buddy_icons/qq/Makefile - pidgin/pixmaps/dialogs/Makefile - pidgin/pixmaps/dialogs/16/Makefile - pidgin/pixmaps/dialogs/16/scalable/Makefile - pidgin/pixmaps/dialogs/64/Makefile - pidgin/pixmaps/dialogs/64/scalable/Makefile - pidgin/pixmaps/emblems/Makefile - pidgin/pixmaps/emblems/16/Makefile - pidgin/pixmaps/emblems/16/scalable/Makefile - pidgin/pixmaps/emotes/Makefile - pidgin/pixmaps/emotes/default/Makefile pidgin/pixmaps/emotes/default/24/Makefile - pidgin/pixmaps/emotes/default/24/scalable/Makefile pidgin/pixmaps/emotes/none/Makefile - pidgin/pixmaps/icons/Makefile - pidgin/pixmaps/icons/16/Makefile - pidgin/pixmaps/icons/16/scalable/Makefile - pidgin/pixmaps/icons/22/Makefile - pidgin/pixmaps/icons/22/scalable/Makefile - pidgin/pixmaps/icons/24/Makefile - pidgin/pixmaps/icons/24/scalable/Makefile - pidgin/pixmaps/icons/32/Makefile - pidgin/pixmaps/icons/32/scalable/Makefile - pidgin/pixmaps/icons/48/Makefile - pidgin/pixmaps/icons/48/scalable/Makefile - pidgin/pixmaps/protocols/Makefile - pidgin/pixmaps/protocols/16/Makefile - pidgin/pixmaps/protocols/16/scalable/Makefile - pidgin/pixmaps/protocols/22/Makefile - pidgin/pixmaps/protocols/22/scalable/Makefile - pidgin/pixmaps/protocols/48/Makefile - pidgin/pixmaps/protocols/48/scalable/Makefile - 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 - pidgin/pixmaps/status/22/Makefile - pidgin/pixmaps/status/22/rtl/Makefile - pidgin/pixmaps/status/22/scalable/Makefile - pidgin/pixmaps/status/32/Makefile - pidgin/pixmaps/status/32/rtl/Makefile - pidgin/pixmaps/status/32/scalable/Makefile - pidgin/pixmaps/status/48/Makefile - pidgin/pixmaps/status/48/rtl/Makefile - pidgin/pixmaps/toolbar/Makefile - pidgin/pixmaps/toolbar/16/Makefile - pidgin/pixmaps/toolbar/16/scalable/Makefile - pidgin/pixmaps/toolbar/22/Makefile - pidgin/pixmaps/toolbar/22/scalable/Makefile - pidgin/pixmaps/tray/Makefile - pidgin/pixmaps/tray/16/Makefile - pidgin/pixmaps/tray/16/scalable/Makefile - pidgin/pixmaps/tray/22/Makefile - pidgin/pixmaps/tray/22/scalable/Makefile - pidgin/pixmaps/tray/32/Makefile - pidgin/pixmaps/tray/48/Makefile pidgin/plugins/Makefile pidgin/plugins/cap/Makefile pidgin/plugins/gestures/Makefile @@ -2230,6 +2249,7 @@ libpurple/protocols/irc/Makefile libpurple/protocols/jabber/Makefile libpurple/protocols/msn/Makefile + libpurple/protocols/msnp9/Makefile libpurple/protocols/myspace/Makefile libpurple/protocols/novell/Makefile libpurple/protocols/null/Makefile @@ -2243,10 +2263,11 @@ libpurple/protocols/yahoo/Makefile libpurple/protocols/zephyr/Makefile libpurple/tests/Makefile + libpurple/purple.h libpurple/version.h - share/Makefile share/sounds/Makefile share/ca-certs/Makefile + finch/finch.pc finch/Makefile finch/libgnt/Makefile finch/libgnt/gnt.pc
--- a/doc/C-HOWTO.dox Sat Nov 17 02:19:52 2007 +0000 +++ b/doc/C-HOWTO.dox Sat Nov 17 02:20:01 2007 +0000 @@ -269,3 +269,4 @@ you may have guessed, this also gets read when libpurple is probing your plugin. If this is missing, the plugin will not load. */ +// vim: syntax=c.doxygen
--- a/doc/account-signals.dox Sat Nov 17 02:19:52 2007 +0000 +++ b/doc/account-signals.dox Sat Nov 17 02:20:01 2007 +0000 @@ -9,8 +9,14 @@ @signal account-setting-info @signal account-set-info @signal account-status-changed + @signal account-authorization-requested + @signal account-authorization-denied + @signal account-authorization-granted + @signal account-error-changed @endsignals + @see account.h + <hr> @signaldef account-added @@ -111,6 +117,7 @@ @return Less than zero to deny the request without prompting, greater than zero if the request should be granted. If zero is returned, then the user will be prompted with the request. + @since 2.3.0 @endsignaldef @signaldef account-authorization-denied @@ -121,6 +128,7 @@ Emitted when the authorization request for a buddy is denied. @param account The account. @param user The name of the user requesting authorization. + @since 2.3.0 @endsignaldef @signaldef account-authorization-granted @@ -131,7 +139,26 @@ Emitted when the authorization request for a buddy is granted. @param account The account. @param user The name of the user requesting authorization. + @since 2.3.0 + @endsignaldef + + @signaldef account-error-changed + @signalproto +void (*account_error_changed)(PurpleAccount *account, const PurpleConnectionErrorInfo *old_error, const PurpleConnectionErrorInfo *current_error); + @endsignalproto + @signaldesc + Emitted when @a account's error changes. + @param account The account whose error has changed. + @param old_error The account's previous error, or @c NULL if it had no + error. After this signal is emitted, @a old_error is + not guaranteed to be a valid pointer. + @param new_error The account's new error, or @c NULL if it has no error. + If not @c NULL, @a new_error will remain a valid until + pointer just after the next time this signal is emitted + for this @a account. + @see purple_account_get_current_error() + @since 2.3.0 @endsignaldef */ -// vim: syntax=c tw=75 et +// vim: syntax=c.doxygen tw=75 et
--- a/doc/blist-signals.dox Sat Nov 17 02:19:52 2007 +0000 +++ b/doc/blist-signals.dox Sat Nov 17 02:20:01 2007 +0000 @@ -13,6 +13,8 @@ @signal blist-node-aliased @endsignals + @see blist.h + <hr> @signaldef buddy-status-changed @@ -105,4 +107,4 @@ @endsignaldef */ -// vim: syntax=c tw=75 et +// vim: syntax=c.doxygen tw=75 et
--- a/doc/certificate-signals.dox Sat Nov 17 02:19:52 2007 +0000 +++ b/doc/certificate-signals.dox Sat Nov 17 02:20:01 2007 +0000 @@ -5,6 +5,8 @@ @signal certificate-deleted @endsignals + @see certificate.h + <hr> @signaldef certificate-stored @@ -28,4 +30,4 @@ @endsignaldef */ -// vim: syntax=c tw=75 et +// vim: syntax=c.doxygen tw=75 et
--- a/doc/cipher-signals.dox Sat Nov 17 02:19:52 2007 +0000 +++ b/doc/cipher-signals.dox Sat Nov 17 02:20:01 2007 +0000 @@ -5,6 +5,8 @@ @signal cipher-removed @endsignals + @see cipher.h + <hr> @signaldef cipher-added @@ -26,4 +28,4 @@ @endsignaldef */ -// vim: syntax=c tw=75 et +// vim: syntax=c.doxygen tw=75 et
--- a/doc/connection-signals.dox Sat Nov 17 02:19:52 2007 +0000 +++ b/doc/connection-signals.dox Sat Nov 17 02:20:01 2007 +0000 @@ -7,6 +7,8 @@ @signal signed-off @endsignals + @see connection.h + <hr> @signaldef signing-on @@ -45,5 +47,16 @@ @param gc The connection that has signed off. @endsignaldef + @signaldef connection-error + @signalproto +void (*connection_error)(PurpleConnection *gc, PurpleConnectionError err, const gchar *desc) + @endsignalproto + @signaldesc + Emitted when a connection error occurs, before @ref signed-off. + @param gc The connection on which the error has occured + @param err The error that occured + @param desc A description of the error, giving more information. + @endsignaldef + */ -// vim: syntax=c tw=75 et +// vim: syntax=c.doxygen tw=75 et
--- a/doc/conversation-signals.dox Sat Nov 17 02:19:52 2007 +0000 +++ b/doc/conversation-signals.dox Sat Nov 17 02:20:01 2007 +0000 @@ -32,6 +32,8 @@ @signal conversation-extended-menu @endsignals + @see conversation.h + @signaldef writing-im-msg @signalproto gboolean (*writing_im_msg)(PurpleAccount *account, const char *who, @@ -427,6 +429,7 @@ conversation. @param conv The conversation. @param list A pointer to the list of actions. + @since 2.1.0 @endsignaldef */ -// vim: syntax=c tw=75 et +// vim: syntax=c.doxygen tw=75 et
--- a/doc/core-signals.dox Sat Nov 17 02:19:52 2007 +0000 +++ b/doc/core-signals.dox Sat Nov 17 02:20:01 2007 +0000 @@ -4,6 +4,8 @@ @signal quitting @endsignals + @see core.h + <hr> @signaldef quitting @@ -15,4 +17,4 @@ @endsignaldef */ -// vim: syntax=c tw=75 et +// vim: syntax=c.doxygen tw=75 et
--- a/doc/dbus-server-signals.dox Sat Nov 17 02:19:52 2007 +0000 +++ b/doc/dbus-server-signals.dox Sat Nov 17 02:20:01 2007 +0000 @@ -5,6 +5,8 @@ @signal dbus-introspect @endsignals + @see dbus-server.h + <hr> @signaldef dbus-method-called @@ -29,4 +31,4 @@ @endsignaldef */ -// vim: syntax=c tw=75 et +// vim: syntax=c.doxygen tw=75 et
--- a/doc/finch.1.in Sat Nov 17 02:19:52 2007 +0000 +++ b/doc/finch.1.in Sat Nov 17 02:20:01 2007 +0000 @@ -123,7 +123,7 @@ Tag (or untag) the current window .TP .B Alt \+ T -Attached all the tag windows to the current workspace +Attached all the tagged windows to the current workspace .TP .B Alt \+ s Show the workspace list @@ -351,6 +351,35 @@ one of \fBa-\fR, \fBalt-\fR, \fBm-\fR or \fBmeta-\fR. You can also use \fBhome\fR, \fBend\fR, \fBleft\fR, \fBright\fR etc. keys. +.SH Menus +You can also specify key-bindings to trigger specific menuitems in windows. For example, the following entry in \fI~/.gntrc\fR will bind \fBCtrl + t\fR to the 'Send IM...' item in the buddylist: + +[buddylist::menu] +.br +c-t = send-im + +The following is the list of IDs of the current menuitems in the buddylist: + +send-im +.br +join-chat +.br +show-empty-groups +.br +show-offline-buddies +.br +sort-status +.br +sort-alpha +.br +sort-log +.br +add-buddy +.br +add-chat +.br +add-group + .SH Mouse Support There is experimental mouse support. You can focus windows, activate buttons, select rows in a list, scroll using the wheel-scroll etc. Selecting text in a
--- a/doc/gtkaccount-signals.dox Sat Nov 17 02:19:52 2007 +0000 +++ b/doc/gtkaccount-signals.dox Sat Nov 17 02:20:01 2007 +0000 @@ -4,6 +4,8 @@ @signal account-modified @endsignals + @see gtkaccount.h + <hr> @signaldef account-modified @@ -15,4 +17,4 @@ @param account The account that has been modified. @endsignaldef */ -// vim: syntax=c tw=75 et +// vim: syntax=c.doxygen tw=75 et
--- a/doc/gtkblist-signals.dox Sat Nov 17 02:19:52 2007 +0000 +++ b/doc/gtkblist-signals.dox Sat Nov 17 02:20:01 2007 +0000 @@ -7,6 +7,8 @@ @signal drawing-tooltip @endsignals + @see gtkblist.h + <hr> @signaldef gtkblist-hiding @@ -52,4 +54,4 @@ a compact tooltip for a non-priority buddy. @endsignaldef */ -// vim: syntax=c tw=75 et +// vim: syntax=c.doxygen tw=75 et
--- a/doc/gtkconv-signals.dox Sat Nov 17 02:19:52 2007 +0000 +++ b/doc/gtkconv-signals.dox Sat Nov 17 02:20:01 2007 +0000 @@ -12,6 +12,8 @@ @signal conversation-displayed @endsignals + @see gtkconv.h + <hr> @signaldef conversation-dragging @@ -125,6 +127,7 @@ @signaldesc Emitted immediately before an existing conversation is hidden. @param gtkconv The PidginConversation + @since 2.2.0 @endsignaldef @signaldef conversation-displayed @@ -132,9 +135,10 @@ void (*conversation_displayed)(PidginConversation *gtkconv); @endsignalproto @signaldesc - Emitted right after the Pidgin UI is reattached to a conversation. + Emitted right after the Pidgin UI is attached to a new or a hidden conversation. @param gtkconv The PidginConversation + @since 2.2.0 @endsignaldef */ -// vim: syntax=c tw=75 et +// vim: syntax=c.doxygen tw=75 et
--- a/doc/gtkimhtml-signals.dox Sat Nov 17 02:19:52 2007 +0000 +++ b/doc/gtkimhtml-signals.dox Sat Nov 17 02:20:01 2007 +0000 @@ -8,6 +8,8 @@ @signal format_function_update @endsignals + @see gtkimhtml.h + <hr> @signaldef url_clicked @@ -57,4 +59,4 @@ @param data User defined data. @endsignaldef */ -// vim: syntax=c tw=75 et +// vim: syntax=c.doxygen tw=75 et
--- a/doc/gtklog-signals.dox Sat Nov 17 02:19:52 2007 +0000 +++ b/doc/gtklog-signals.dox Sat Nov 17 02:20:01 2007 +0000 @@ -4,6 +4,8 @@ @signal log-displaying @endsignals + @see gtklog.h + <hr> @signaldef log-displaying @@ -17,4 +19,4 @@ @endsignaldef */ -// vim: syntax=c tw=75 et +// vim: syntax=c.doxygen tw=75 et
--- a/doc/imgstore-signals.dox Sat Nov 17 02:19:52 2007 +0000 +++ b/doc/imgstore-signals.dox Sat Nov 17 02:20:01 2007 +0000 @@ -4,6 +4,8 @@ @signal image-deleting @endsignals + @see imgstore.h + <hr> @signaldef image-deleting @@ -11,7 +13,7 @@ char *(*image_deleting)(const PurpleStoredImage *img); @endsignalproto @signaldesc - Emitted when a PurpleStoredImage is about to be destroyed. This allows + Emitted when a #PurpleStoredImage is about to be destroyed. This allows for what amounts to weak references. Code can hold onto a pointer to the PurpleStoredImage without actually "holding" a reference. They can then use a signal handler to let them know when their img is about to @@ -21,4 +23,4 @@ @endsignaldef */ -// vim: syntax=c tw=75 et +// vim: syntax=c.doxygen tw=75 et
--- a/doc/log-signals.dox Sat Nov 17 02:19:52 2007 +0000 +++ b/doc/log-signals.dox Sat Nov 17 02:20:01 2007 +0000 @@ -4,6 +4,8 @@ @signal log-timestamp @endsignals + @see log.h + <hr> @signaldef log-timestamp @@ -21,4 +23,4 @@ @endsignaldef */ -// vim: syntax=c tw=75 et +// vim: syntax=c.doxygen tw=75 et
--- a/doc/notify-signals.dox Sat Nov 17 02:19:52 2007 +0000 +++ b/doc/notify-signals.dox Sat Nov 17 02:20:01 2007 +0000 @@ -6,6 +6,8 @@ @signal displaying-emails-notification @endsignals + @see notify.h + @signaldef displaying-userinfo @signalproto void (*displaying_userinfo)(PurpleAccount *account, const char *who, PurpleNotifyUserInfo *user_info); @@ -33,6 +35,7 @@ @param from Who the email is from. @param to Who the email is to. @param url A url to view the email. + @since 2.1.0 @endsignaldef @signaldef displaying-emails-notification @@ -50,7 +53,8 @@ @param tos Who the emails are to. @param urls The urls to view the emails. @param count Number of emails being notified of. + @since 2.1.0 @endsignaldef */ -// vim: syntax=c tw=75 et +// vim: syntax=c.doxygen tw=75 et
--- a/doc/pidgin.1.in Sat Nov 17 02:19:52 2007 +0000 +++ b/doc/pidgin.1.in Sat Nov 17 02:20:01 2007 +0000 @@ -17,8 +17,8 @@ .\" .\" You should have received a copy of the GNU General Public .\" License along with this manual; if not, write to the Free -.\" Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, -.\" USA. +.\" Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +.\" Boston, MA 02111-1301 USA. .TH pidgin 1 .SH NAME Pidgin v@VERSION@ \- Instant Messaging client @@ -32,7 +32,7 @@ which is capable of connecting to AIM, MSN, Yahoo!, XMPP, ICQ, IRC, SILC, Novell GroupWise, Lotus Sametime, Zephyr, Gadu-Gadu, and QQ all at once. It has many common features found in other clients, as well as many unique features. -Finch is not endorsed by or affiliated with America Online, ICQ, Microsoft, or +Pidgin is not endorsed by or affiliated with America Online, ICQ, Microsoft, or Yahoo. .SH OPTIONS @@ -49,28 +49,31 @@ .B \-h, \-\-help Print a summary of command line options and exit. .TP +.B \-m, \-\-multiple +Allow multiple instances of Pidgin to run. +.TP .B \-n, \-\-nologin Don't automatically login when Pidgin starts. Sets the global status to \fBOffline\fR. .TP .B \-l, \-\-login[=\fINAME\fR,\fINAME\fR,...] Sign in the comma-separated list of accounts provided, in addition to the -accounts that would be logged in anyway. If you do not specify such a -comma-separated list, your first account will be signed in. +accounts that would be logged in anyway. If the user does not specify such a +comma-separated list, the first account in accounts.xml will be signed in. .TP .B \-v, \-\-version Print the current version and exit. .SH BUDDY LIST The \fBBuddy List\fR window is Pidgin's main interface window. Using -this window you can see which of your buddies is online, away, idle, etc. -You can also add and remove buddies from your buddy list. +this window a user can see which of his/her buddies is online, away, idle, +etc. The user can also add buddies to and remove buddies from the buddy list. -The \fBBuddy List\fR window contains a list of your buddies who are online -and have allowed you to be notified of their presence. The icon to the -left of each buddy indicates the buddy's current state and the protocol -they are using. Double clicking a buddy will open a new \fBConversation\fR -window. Right clicking will pop up a menu: +The \fBBuddy List\fR window contains a list of the user's buddies who are +online and have allowed the user to be notified of their presence. The icon +to the left of each buddy indicates the buddy's current status. Double +clicking a buddy will open a new \fBConversation\fR window. Right clicking +will pop up a menu: .TP .B Get Info Retrieves and displays information about the buddy. This information is @@ -89,16 +92,17 @@ discussed later. .TP .B View Log -Pidgin is capable of automatically log its activities. These logs are +Pidgin is capable of automatically logging messages. These logs are either plain text files (with a .txt extension) or html files (with a \&.html extension) located under the \fI~/.purple/logs\fR directory. This menu command will display Pidgin's log viewer with logs loaded for that buddy or chat. .TP .B Alias -Create an alias for this buddy. This will open up a new dialog in which -one can give this buddy an alternate name to appear on the buddy list and -in conversations. +Create an alias for this buddy. This will show an editable text field where +the buddy's screen name was displayed. In this field one can give this +buddy an alternate, more friendly name to appear on the buddy list and in +conversations. For example, if a buddy's name screen name was jsmith1281xx and his real name was 'John Q. Smith,' one could create an alias as to identify the @@ -106,39 +110,27 @@ .LP The remainder of the menu will consist of protocol specific commands. These commands vary depending on the protocol. -.LP -At the bottom of the \fBBuddy List\fR are several buttons (if enabled in -\fBPreferences\fR): .TP -.B IM -Opens a new \fBConversation\fR window to to the selected buddy, or brings -up the \fBNew Message\fR dialog box if no buddy is selected. -.TP -.B Info -Retrieves and display information about the selected buddy, or brings up -the \fBGet User Info\fR dialog box if no buddy is selected. -.TP -.B Chat -Brings up the \fBJoin Chat\fR dialog box, prompting the user to select -which username to use and what chat group to join. -.TP -.B Away -Brings up a menu of all available \fBAway Messages\fR. If an item is -selected, all online accounts will use this item as their away message. +.B Status Selector +At the bottom of the \fBBuddy List\fR is a status selector which allows one to +change his/her status. This will be discussed further in the \fBSTATUS +MESSAGES\fR section below. .SH ACCOUNT EDITOR The account editor consists of a list of accounts and information about -them. Clicking \fIDelete\fR will delete the currently selected account. +them. It can be accessed by selecting \fBManage\fR from the Tools menu. +Clicking \fIDelete\fR will delete the currently selected account. Clicking \fIAdd\fR or \fIModify\fR will invoke a \fBModify Account\fR -window. Here, you can add or alter account information. When creating a -new account, you will submit your screen name and password. You will also -choose your protocol. +window. Here, the user can add or alter account information. When creating +a new account, the user will submit a screen name and password. The user will +also choose the protocol for the account. If \fIRemember Password\fR is chosen, the password will be saved in -Pidgin's configuration file. +Pidgin's \fI~/.purple/accounts.xml\fR configuration file. -If \fIAuto-Login\fR is chosen, this account will automatically login upon -starting Pidgin. +If \fIEnabled\fR is checked in the accounts dialog, this account will +follow the status currently selected in the status selector. If it is +not checked, the account will always be offline. Each protocol has its own specific options that can be found in the modify screen. @@ -149,120 +141,84 @@ .SH Interface -\fIDisplay remote nicknames if no alias is set\fR: Toggles whether server -nickname data should be used if no local alias exists. - -.SH Buddy List -\fISorting\fR: Toggles the order in which buddies are shown in your -\fBBuddy List\fR between none, alphabetical, by status and by log size. +.TP +.B Show system tray icon +Specifies when to show a Pidgin icon in the notification area of the user's +panel (commonly referred to as the System Tray). -\fIShow buttons as\fR: Toggles between picture-only, text-only, picture and -text or no buttons view of the buttons on the \fBBuddy List\fR. - -\fIRaise window on events\fR: Tells Pidgin to bring the \fBBuddy -List\fR window to the top when buddies sign in or out. - -\fIShow numbers in groups\fR: The number of buddies from each group -currently logged in will be shown along with the total number of buddies in -the group. +.TP +.B Hide new IM conversations +Specifies when to hide new IM messages. Messages will queue under the +specified condition until shown. Clicking the Pidgin icon in the +notification area or system tray will display the queued messages. An +icon also appears in the buddy list's menu bar; this icon may also be +used to display queued messages. -\fIShow buddy icons\fR: Toggles the display of buddies' custom icons. - -\fIShow warning levels\fR: Each buddy's warning level will be displayed -next to the screen name. As a buddy's warning level increases, outgoing -messages are more and more severely rate-limited. +.TP +.B Show IMs and chats in tabbed windows +When checked, this option will cause IM and chat sessions to appear in +windows with multiple tabs. One tab will represent one conversation or +chat. Where tabs are placed will be dictated by the preferences below. -\fIShow idle times\fR: The amount of time each buddy has been idle will be -displayed next to the screen name (if the buddy has opted to have their -client report this information). +.TP +.B Show close buttons on tabs +When checked, this option will cause a clickable "U+2715 MULTIPLICATION X" +unicode character to appear at the right edge of each tab. Clicking this +will cause the tab to be closed. -\fIDim idle buddies\fR: If enabled, idle buddies will be displayed in grey -text instead of black text. +.TP +.B Placement +Specifies where to place tabs in the window. Some tab orientations may +allow some users to fit more tabs into a single window comfortably. -\fIAutomatically expand contacts\fR: If enabled, contacts will -automatically expand to show the associated buddies when the mouse is held -over the contact for a short period. +.TP +.B New conversations +Specifies under which conditions tabs are placed into existing windows or +into new windows. For a single window, select \fILast created window\fR here. .SH Conversations -\fIShow buttons as...\fR: The selected item will determine whether -picture-only, text-only, combined picture/text, or no buttons will be used -for \fBConversation\fR windows. - -\fIShow formatting toolbar\fR: Display the formatting toolbar between the -upper and lower text boxes in conversations. - -\fIShow aliases in tabs/titles\fR: Displays buddy alias instead of screen -name in window tabs and titles. - -\fIShow buddy icons\fR: For protocols that support it, buddy icons allow -buddies to send small pictures to be displayed during the course of a -conversation. Turning this option off hides those pictures. - -\fIEnable buddy icon animation\fR: If these pictures happen to be animated, -this option will enable the animation, otherwise only the first frame will -be displayed. - -\fINotify buddies that you are typing to them\fR: Some protocols allow -clients to tell their buddies when they are typing. This option enables -this feature for protocols that supports it. - -\fIRaise IM windows on events\fR: If enabled, IM \fBConversation\fR windows -will be brought to the top when new messages are received. - -\fIRaise Chat windows on events\fR: If enabled, chat \fBConversation\fR windows -will be brought to the top when new messages are received. - -\fIUse multi-colored screen names in chats\fR: Color code the screen names of -users in chat rooms. +.TP +.B Enable buddy icon animation +If a buddy's icon happens to be animated, this option will enable the +animation, otherwise only the first frame will be displayed. .TP -.B Tab Options -\fIShow IMs and chats in tabbed windows\fR: Tabbed chatting allows one to -have multiple conversations without multiple windows. - -\fIShow close buttons on tabs\fR: Adds a close button to each tab. - -\fITab Placement...\fR: Specifies where tabs are shown in the conversation -window. - -\fI New conversation placement...\fR: Determines where new conversations will -be placed (Last created window / New window / windows grouped by group or -account / separate windows for IMs and Chats). +.B Notify buddies that you are typing to them +Some protocols allow clients to tell their buddies when they are typing. +This option enables this feature for protocols that supports it. For XMPP, +this also enables sending the "User has left the conversation" message +when ending the conversation. .TP -.B Message Text -\fIShow timestamp on messages\fR: Toggles the timestamp behavior for -conversations. Per-conversation behavior can be changed by pressing -\fIF2\fR in the \fBConversation\fR window. - -\fIHighlight misspelled words\fR: Toggles highlighting of misspelled words -as you type. - -\fIIgnore colors/font faces/font sizes\fR: Tells Pidgin to disregard -buddies' color/font/size information in displaying IMs or Chats. +.B Default Formatting +Allows specifying the default formatting to apply to all outgoing messages +(only applicable to protocols that support formatting in messages). -\fIDefault Formatting\fR: Allows specifying the default formatting to apply -to all outgoing messages (only applicable to protocols that support -formatting in messages). - -.TP -.B Shortcuts -Allows the user to determine which keyboard shortcuts are available. - -.TP -.B Smiley Themes +.SH Smiley Themes Allows the user to choose between different smiley themes. The "none" theme will disable graphical emoticons - they will be displayed as text instead. +The \fBAdd\fR and \fBRemove\fR buttons may be used to install or uninstall +smiley themes. Themes may also be installed by dragging and dropping them +onto the list of themes. .SH Sounds -\fISounds while away\fR: Determines whether sounds are played when an away -message is up. +.TP +.B Method +Lets the user choose between different playback methods. The user can also +manually enter a command to be executed when a sound is to be played\ +(\fI%s\fR expands to the full path to the file name). -\fISound Method\fR lets the user choose between different playback methods. -The user can also manually enter a command to be executed when a sound is -to be played (\fI%s\fR expands to the full path to the file name). +.TP +.B Sounds when conversation has focus +When checked, sounds will play for events in the active conversation if +the window is focused. When unchecked, sounds will not play for the +active conversation when the window is focused. + +.TP +.B Enable Sounds +Determines when to play sounds. .TP .B Sound Events @@ -271,17 +227,28 @@ .SH Network .TP -.B IP Address -\fIAutodetect IP Address\fR: Pidgin will attempt to automatically determine -your IP address for use in file transfers and Direct IMs. - -\fIPublic IP\fR: What IP address to use for file transfer and Direct IMs. This -is mainly useful for users with multiple network interfaces or behind NAT. +.B STUN server +This allows specifying a server which uses the STUN protocol to determine +a host's public IP address. This can be particularly useful for some +protocols. .TP -.B Ports -\fIManually specify range of ports to listen on\fR: Specify specific ports to -listen on, overriding any defaults. +.B Autodetect IP address +When checked, causes Pidign to attempt to determine the public IP address +of the host on which Pidgin is running and disables the \fBPublic IP\fR +text field listed below. + +.TP +.B Public IP +If \fBAutodetect IP address\fR is disabled, this field allows manually +specifying the public IP address for the host on which Pidgin is running. +This is mainly useful for users with multiple network interfaces or behind +NATs. + +.TP +.B Manually specify range of ports to listen on +Specify a range ports to listen on, overriding any defaults. This is +sometimes useful for file transfers and Direct IM. .TP .B Proxy Server @@ -290,134 +257,108 @@ .SH Browser +.TP +.B Browser Allows the user to select Pidgin's default web browser. Firefox, Galeon, Konqueror, Mozilla, Netscape and Opera are supported natively. The user can also manually enter a command to be executed when a link is clicked (\fI%s\fR expands to the URL). For example, \fIxterm -e lynx "%s"\fR will -open the link with lynx. \fIOpen new window by default\fR makes the -browser use a new window instead of using the current window (or spawning a -new tab). +open the link with lynx. + +.TP +.B Open link in +Allows the user to specify whether to use an existing window, a new tab, a +new window, or to let the browser to decide what to do when calling the +browser to open a link. Which options are available will depend on which +browser is selected. .SH Logging -\fIMessage Logs\fR lets the user choose whether \fBConversations\fR and/or -\fBBuddy Chats\fR will be logged as well as whether logs will be in HTML or -plain text format. \fISystem Logs\fR describes the types of events to be -logged. +.TP +.B Log format +Specifies how to log. Pidgin supports HTML and plain text, but plugins can +provide other logging methods. -.SH Away / Idle - -\fIQueue new messages when away\fR: Messages received since going Away will -not be shown until away status is removed. +.TP +.B Log all instant messages +When enabled, all IM conversations are logged. This can be overridden on a +per-conversation basis in the conversation window. -\fISend auto-response\fR: If someone messages you while away, your -auto-response will be sent. +.TP +.B Log all chats +When enabled, all chat conversations are logged. This can be overridden on a +per-conversation basis in the conversation window. -\fIOnly send auto-response when idle\fR: If someone messages you while -away, your auto-response will only be sent if Pidgin decides that the -connection is idle. +.TP +.B Log all status changes to system log +When enabled, status changes are logged. -\fIIdle time reporting\fR: If \fINone\fR is selected, account idle time -will not be reported. \fIPidgin usage\fR infers your idle time from your -usage of Pidgin. \fIX usage\fR infers your idle time from \fBX\fR -(this option may not be universally available). +.SH Status / Idle -\fIAuto-away\fR: Determines if and under what conditions Pidgin will -automatically turn on the Away status. +.TP +.B Report idle time +Determines under which conditions to report idle time. \fBBased on keyboard +and mouse use\fR uses keyboard and mouse activity to determine idle time. +\fBFrom last sent message\fR uses the time at which the user last sent a +message in Pidgin to determine idle. \fBNever\fR disables idle reporting. .TP -.B Away Messages -Lets the user add/edit/remove available \fBAway Messages\fR. +.B Auto-reply +Determines when to send an auto-reply on protocols which support it +(currently only AIM). -.SH Plugins +.TP +.B Change status when idle +When enabled, this uses the \fBMinutes before becoming idle\fR and \fBChange +status to\fR preferences described below to set status on idle. + +.TP +.B Minutes before becoming idle +Specifies how many minutes of inactivity are required before considering the +user to be idle. -Allows the user to enable add-on plugins for Pidgin. Several of these -come with Pidgin, while others must be downloaded separately. The -\fIDescription\fR field gives the plugin author's description of the -plugin, while the \fIDetails\fR field gives the plugin's authorship, URL, -and file name/location information. +.TP +.B Change status to +Specifies which "primitive" or "saved" status to use when setting status on +idle. -Some plugins can be configured. If you load such a plugin, its -configuration preferences will appear as a submenu to \fBPlugins\fR, with -the submenu title determined by the plugin's name. +.TP +.B Use status from last exit at startup +If this is checked, Pidgin will remember what status was active when the +user closed Pidgin and restore it at the next run. When disabled, Pidgin +will always set the status selected in \fBStatus to apply at startup\fR +at startup. -.SH Protocols - -Protocols provide protocol specific preferences here. +.TP +.B Status to apply at startup +When \fBUse status from last exit at startup\fR is disabled, this specifies +which "primitive" or "saved" status to use at startup. .SH CONVERSATIONS When starting a new conversation, the user is presented with the \fBConversation\fR window. The conversation appears in the upper text box and the user types his/her message in the lower text box. Between the two -is a row of settings, represented by icons. Some or all buttons may not be +is a row of options, represented by icons. Some or all buttons may not be active if the protocol does not support the specific formatting. From left to right: .TP -.B Bold -Turns on/off bold. -.TP -.B Italics -Turns on/off italics. -.TP -.B Underline -Turns on/off underline. -.TP -.B Decrease font size -Increases the size of the message text. -.TP -.B Increase font size -Decreases the size of the message text. +.B Font +This menu provides font control options for the current conversation. Size, +style, and face may be configured here. .TP -.B Select a foreground color -Changes the foreground color of the message text. -.TP -.B Select a background color -Changes the background color of the message text. -.TP -.B Add image -Inserts an in-line image in the message. -.TP -.B Add hyperlink -Adds a clickable link to the message. -.TP -.B Add smiley -Adds an emoticon (smiley) to your message. - -Beneath the lower text box is a row of buttons that execute commands: +.B Insert +This menu provides the ability to insert images, horizontal rules, and links +where the protocol supports each of these features. .TP -.B Warn -This issues a warning to the other person in the conversation (not -available in all protocols). -.TP -.B Block -This adds the other person to your deny list (not available in all -protocols). -.TP -.B Send File -Send a file to this user. This option is only available on protocols where -Pidgin supports file transfer. -.TP -.B Add -This adds this user to your buddy list. This option is not available if -the user is already on your list. -.TP -.B Remove -This removes this user from your buddy list. This option is not available -if the user is not on your list. -.TP -.B Info -This gets information (a profile) about the other person in the -conversation (not available in all protocols). -.TP -.B Send -This sends what's currently in the lower text box +.B Smile! +Allows the insertion of graphical smileys via the mouse. This button shows +the user a dialog with the available smileys for the current conversation. -.SH BUDDY CHATS -For protocols that allow it, \fBBuddy Chats\fR can be entered through the -\fIFile\fR menu or the \fIChat\fR button at the bottom of the \fBBuddy -List\fR's \fBOnline\fR tab. +.SH CHATS +For protocols that allow it, \fBChats\fR can be entered through the +\fIBuddies\fR menu. -Additional commands available in chat, depending on the protocol are: +Additional features available in chat, depending on the protocol are: .TP .B Whisper The text will appear in the chat conversation, but it will only be visible @@ -432,78 +373,156 @@ .B Set Topic Set the topic of the chat room. This is usually a brief sentence describing the nature of the chat--an explanation of the chat room's name. +.TP +.B Private Message (IM) +Send a message to a specific person in the chat. Messages sent this way will +not appear in the chat window, but instead open a new IM conversation. -.SH AWAY MESSAGES -Most protocols allow for away messages. When a user is \fIAway\fR, he can -leave an informative message for others to see. The \fBAway\fR submenu of -the \fBTools\fR menu is used to add and remove away messages. +.SH STATUS MESSAGES +Most protocols allow for status messages. By using status messages, a user +can leave an informative message for others to see. Status and status +messages are configured via the status selector at the bottom of the Buddy +List window. By default the menu shown here is divided into sections for +"primitive" status types, such as \fIAvailable\fR, \fIAway\fR, etc.; a few +"popular" statuses (including "transient" statuses) which have been +recently used, and a section which shows \fBNew Status...\fR and \fBSaved +Statuses...\fR options for more advanced status manipulation. + +.TP +.B Primitive Statuses +A primitive status is a basic status supported by the protocol. Examples of +primitive statuses would be Available, Away, Invisible, etc. A primitive +status can be used to create a \fBTransient Status\fB or a \fBSaved Status\fR, +both explained below. Essentially, primitive statuses are building blocks +of more complicated statuses. + +.TP +.B Transient Statuses +When one of the statuses from the topmost section of the status selector's +menu is selected, this creates a transient, or temporary, status. The status +will show in the "popular statuses" section in the menu until it has not been +used for a sufficiently long time. A transient status may also be created by +selecting \fINew Status...\fR from the status selector's menu, then clicking +\fIUse\fR once the user has entered the message. -\fINew Away Message\fR provides space for one to enter an away message and -a title for that message. If \fISave\fR or \fISave & Use\fR are chosen, -this message will be saved. It can later be referred to by the title given -to it. +.TP +.B Saved Statuses +Saved statuses are permanent--once created, they will exist until deleted. +Saved statuses are useful for statuses and status messages that will be used +on a regular basis. They are also useful for creating complex statuses in +which some accounts should always have a different status from others. For +example, one might wish to create a status called "Sleeping" that has all +accounts set to "Away", then create another status called "Working" that +has three accounts set to "Away" and another account set to "Available." + +.TP +.B New Status Window +When the user selects \fINew Status...\fR from the status selector menu, +Pidgin presents the user with a dialog asking for status-related information. +That information is discussed below: + +\fITitle\fR - The name of the status that will appear in the status selctor's +menu. If the user clicks the \fISave\fR or \fISave & Use\fR button, this +name will also be shown in the \fBSaved Status Window\fR. The title should +be a short description of the status. + +\fIStatus\fR - The type of status being created, such as Available, Away, etc. -\fIRemove Away Message\fR is a submenu containing the titles of saved away -messages. Clicking on one of these titles will remove the away message -associated with it. +\fIMessage\fR - The content of the status message. This is what is visible +to other users. Some protocols will allow formatting in some status messages; +where formatting is not supported it will be stripped to the bare text entered. -The rest of the \fIaway\fR menu provides the user with a way to assign -different away messages to different connections. Choosing \fISet All -Away\fR will set away all the connections capable of the away state. +\fIUse a different status for some accounts\fR - This allows the creation of +complex statuses in which some accounts' status differs from that of other +accounts. To use this, the user will click the expander to the left of the +text, then select individual accounts which will have a different status +and/or status message. When the user selects an account, Pidgin will present +another status dialog asking for a status and a message just for the selected +account. + +.TP +.B Saved Status Window +When the user selects \fISaved Statuses...\fR from the status selector's menu, +Pidgin presents a dialog that lists all saved statuses. "Transient" statuses, +discussed above, are \fB\fINOT\fR\fR shown here. This window provides the +ability to manage saved statuses by allowing the creation, modification, and +deletion of saved statuses. The \fIUse\fR, \fIModify\fR, and \fIDelete\fR +buttons here allow operation on the status selected from the list; the \fAdd\fR +button allows creation of a new saved status, and the \fIClose\fR button closes +the window. .SH BUDDY POUNCE A Buddy Pounce is an automated trigger that occurs when a buddy returns to -a normal state from an away state. The \fBNew Buddy Pounce\fR dialog box -can be activated by selecting the \fIBuddy Pounce\fR submenu from the -\fBTools\fR menu. A pounce can be set to occur on any combination of the -events listed, and any combination of actions can result. If \fISave this -pounce after activation\fR is checked, the trigger will remain until it is -removed from the \fIRemove Buddy Pounce\fR menu. +a normal state from an away state. The \fBBuddy Pounce\fR dialog box +can be activated by selecting the \fIBuddy Pounce\fR option from the +\fBTools\fR menu. From this dialog, new pounces can be created with the +\fBAdd\fR button and existing pounces can be removed with the \fBDelete\fR +button. A pounce can be set to occur on any combination of the +events listed, and any combination of actions can result. If \fIPounce +only when my status is not Available\fR is checked, the pounce will occur +only if the user is set to a non-available status, such as invisible, do not +disturb, away, etc. If \fIRecurring\fR is checked, the pounce will remain +until removed by the \fBDelete\fR button. .SH PLUGINS Pidgin allows for dynamic loading of plugins to add extra functionality -to Pidgin. Plugins can be enabled and configured from the -\fBPreferences\fR window. See \fIplugins/HOWTO\fR for more information on -writing plugins. +to Pidgin. See \fIplugins/HOWTO\fR or +\fIhttp://developer.pidgin.im/wiki/CHowTo\fR for information on writing +plugins. + +The plugins dialog can be accessed by selecting Plugins from the Tools menu. +Each plugin available appears in this dialog with its name, version, and a +short summary of its functionality. Plugins can be enabled with the checkbox +beside the name and short description. More information on the currently +selected plugin is available by clicking the expander beside the text +\fIPlugin Details\fR. If the selected plugin has preferences or configuration +options, the \fIConfigure Plugin\fR button will present the plugin's +preferences dialog. .SH PERL -Pidgin allows for perl scripting. See \fIPerl Scripting HOWTO\fR in -the Pidgin documentation for more information about perl scripting. +Pidgin allows for plugins to be written in the perl scripting language. See +\fIPerl Scripting HOWTO\fR in the Pidgin documentation for more information +about perl scripting. .SH TCL -Pidgin allows for Tcl scripting. See \fIplugins/tcl/TCL-HOWTO\fR for -more information about Tcl scripting. +Pidgin allows for plugins to be written in the Tcl scripting language. See +\fIplugins/tcl/TCL-HOWTO\fR for more information about Tcl scripting. + +.SH D-Bus +Pidgin allows for interaction via D-Bus. Currently very little documentation +about this interaction exists. .SH FILES -\fI@prefix@/bin/pidgin\fR: Pidgin's location. + \fI@prefix@/bin/pidgin\fR: Pidgin's location. .br -\fI@prefix@/lib/pidgin/\fR: Pidgin's plugins directory. + \fI~/.purple/blist.xml\fR: the buddy list. .br -\fI~/.purple/prefs.xml\fR: Pidgin's configuration file. + \fI~/.purple/accounts.xml\fR: information about the user's accounts. .br -\fI~/.purple/accounts.xml\fR: information about your accounts. + \fI~/.purple/pounces.xml\fR: stores the user's buddy pounces. +.br + \fI~/.purple/prefs.xml\fR: Pidgin's configuration file. .br -\fI~/.purple/status.xml\fR: stores your away messages. -.br -\fI~/.purple/pounces.xml\fR: stores your buddy pounces. + \fI~/.purple/status.xml\fR: stores the user's away messages. .br -\fI~/.purple/logs/PROTOCOL/ACCOUNT/SCREENNAME/DATE.{html,txt}\fR: conversation logs. + \fI~/.purple/logs/PROTOCOL/ACCOUNT/SCREENNAME/DATE.{html,txt}\fR: conversation logs. + +.SH DIRECTORIES + \fI@prefix@/lib/pidgin/\fR: Pidgin's plugins directory. .br -\fI~/.purple/blist.xml\fR: the buddy list. + \fI@prefix@/lib/purple-2/\fR: libpurple's plugins directory. .br -\fI~/.purple/plugins/\fR: users local plugins + \fI~/.purple\fR: users' local settings +.br + \fI~/.purple/plugins/\fR: users' local plugins .SH BUGS -The bug tracker can be reached by visiting: -.br -\fIhttp://developer.pidgin.im/report\fR +The bug tracker can be reached by visiting \fIhttp://developer.pidgin.im/query\fR .SH PATCHES If you fix a bug in Pidgin (or otherwise enhance it), please submit a patch (using \fImtn diff > my.diff\fR against the latest version from the -Monotone repository) at -.br -\fIhttp://developer.pidgin.im/newticket\fR +Monotone repository) at \fIhttp://developer.pidgin.im/simpleticket\fR Before sending a bug report, please verify that you have the latest version of Pidgin. Many bugs (major and minor) are fixed @@ -530,86 +549,109 @@ .SH AUTHORS Pidgin's active developers are: - - Sean Egan (lead developer) <\fIseanegan@gmail.com\fR> +.br + Sean Egan (lead developer) <\fIseanegan@gmail.com\fR> .br - Daniel 'datallah' Atallah (developer) + Daniel 'datallah' Atallah (developer) .br - Ethan 'Paco-Paco' Blanton (developer) + John 'rekkanoryo' Bailey (developer) .br - Thomas Butter (developer) + Ethan 'Paco-Paco' Blanton (developer) .br - Ka-Hing Cheung (developer) + Thomas Butter (developer) .br - Sadrul Habib Chowdhury (developer) + Ka-Hing Cheung (developer) +.br + Sadrul Habib Chowdhury (developer) .br - Mark 'KingAnt' Doliner (developer) <\fIthekingant@users.sourceforge.net\fR> + Mark 'KingAnt' Doliner (developer) <\fIthekingant@users.sourceforge.net\fR> .br - Christian 'ChipX86' Hammond (developer & webmaster) <\fIchipx86@chipx86.com\fR> + Casey Harkins (developer) .br - Gary 'grim' Kramlich (developer) + Gary 'grim' Kramlich (developer) .br - Richard 'rlaager' Laager (developer) <\fIrlaager@pidgin.im\fR> + Richard 'rlaager' Laager (developer) <\fIrlaager@pidgin.im\fR> .br - Richard 'wabz' Nelson (developer) + Richard 'wabz' Nelson (developer) +.br + Christopher 'siege' O'Brien (developer) .br - Christopher 'siege' O'Brien (developer) + Bartosz Oler (developer) .br - Bartosz Oler (developer) + Etan 'deryni' Reisner (developer) .br - Etan 'deryni' Reisner (developer) + Tim 'marv' Ringenbach (developer) <\fImarv_sf@users.sf.net\fR> .br - Tim 'marv' Ringenbach (developer) <\fImarv_sf@users.sf.net\fR> + Luke 'LSchiere' Schierer (support) .br - Luke 'LSchiere' Schierer (support) + Megan 'Cae' Schneider (support/QA) .br - Megan 'Cae' Schneider (support/QA) + Evan Schoenberg (developer) .br - Evan Schoenberg (developer) + Kevin 'SimGuy' Stange (developer and webmaster) .br - Stu 'nosnilmot' Tomlinson (developer) + Stu 'nosnilmot' Tomlinson (developer) .br - Nathan 'faceprint' Walp (developer) + Nathan 'faceprint' Walp (developer) .br Our crazy patch writers include: - -John 'rekkanoryo' Bailey +.br + Dennis 'EvilDennisR' Ristuccia .br -Felipe 'shx' Contreras + Peter 'fmoo' Ruibal .br -Decklin Foster + Gabriel 'Nix' Schulhof .br -Casey Harkins -.br -Peter 'Bleeter' Lawler + Will 'resiak' Thompson .br -Robert 'Robot101' McQueen + + +Our artists are: .br -Benjamin Miller -.br -Kevin 'SimGuy' Stange + Hylke Bons <\fIh.bons@student.rug.nl\fR> .br -The retired developers of \fBgaim\fR are: - - Herman Bloggs (win32 port) <\fIherman@bluedigits.com\fR> +Our retired developers are: +.br + Herman Bloggs (win32 port) <\fIherman@bluedigits.com\fR> .br - Jim Duchek <\fIjim@linuxpimps.com\fR> (maintainer) + Jim Duchek <\fIjim@linuxpimps.com\fR> (maintainer) .br - Rob Flynn <\fIgaim@robflynn.com\fR> (maintainer) + Rob Flynn <\fIgaim@robflynn.com\fR> (maintainer) +.br + Adam Fritzler (libfaim maintainer) .br - Adam Fritzler (libfaim maintainer) + Christian 'ChipX86' Hammond (developer & webmaster) <\fIchipx86@chipx86.com\fR> .br - Syd Logan (hacker and designated driver [lazy bum]) + Syd Logan (hacker and designated driver [lazy bum]) .br - Jim Seymour (Jabber developer) + Jim Seymour (XMPP developer) .br - Mark Spencer (original author) <\fImarkster@marko.net\fR> + Mark Spencer (original author) <\fImarkster@marko.net\fR> .br - Eric Warmenhoven (former lead developer) <\fIeric@warmenhoven.org\fR> + Eric Warmenhoven (former lead developer) <\fIeric@warmenhoven.org\fR> .br -This manpage was originally written by Dennis Ristuccia <\fIdennis@dennisr.net\fR>. It has been updated and largely rewritten by Sean Egan <\fIseanegan@gmail.com\fR> and Ben Tegarden <\fItegarden@uclink.berkeley.edu\fR>. + +Our retired crazy patch writers include: +.br + Felipe 'shx' Contreras +.br + Decklin Foster +.br + Peter 'Bleeter' Lawler +.br + Robert 'Robot101' McQueen +.br + Benjamin Miller +.br + + +This manpage was originally written by Dennis Ristuccia +<\fIdennis@dennisr.net\fR>. It has been updated and largely rewritten by +Sean Egan <\fIseanegan@gmail.com\fR>, +Ben Tegarden <\fItegarden@uclink.berkeley.edu\fR>, +and John Bailey <\fIrekkanoryo@pidgin.im\fR>.
--- a/doc/plugin-ids.dox Sat Nov 17 02:19:52 2007 +0000 +++ b/doc/plugin-ids.dox Sat Nov 17 02:20:01 2007 +0000 @@ -2,10 +2,10 @@ @section Introduction Every plugin contains a unique identifier to prevent duplicate plugin - loading and conflicts. This, which will be called a plugin ID from here - on, must follow a specific format. This format categorizes a plugin and - makes duplicate IDs unlikely. - + loading and conflicts. Third-party plugins (that is, plugins written by + anyone who is not a libpurple, Pidgin, or Finch developer) are expected + to use a plugin ID that follows a specific format. This format + categorizes plugins and makes duplicate IDs highly unlikely. @section Format The basic format of a plugin ID is as follows: @@ -15,27 +15,79 @@ The @em type indicator specifies the type of plugin. This must be one of the following: - - core - Core plugin, capable of being loaded in any program using - libpurple. It must not use any UI-specific code. - - prpl - Protocol plugin, providing additional protocols to - connect to. - - lopl - Loader plugin, which loads scripts as plugins (like Perl - or TCL). - - gtk - GTK+ 2.x plugin. It may use GTK+ code, but cannot use any - window toolkit code (such as X11 or Win32). - - gtk-x11 - GTK+ 2.x plugin using X11 code. - - gtk-win32 - GTK+ 2.x plugin using Win32 code. - - qpe - Gaim for Qtopia plugin. + - core - A core libpurple plugin, capable of being loaded in any + program using libpurple. Core plugins may not contain any + UI-specific code. + - prpl - A protocol plugin. This is a special type of core plugin, + which provides libpurple the ability to connect to + another IM or chat network. + - lopl - A loader plugin, which loads scripts as plugins. Perl and + Tcl plugins are made possible by loader plugins. + - gtk - A GTK+ 2.x (a.k.a. Pidgin) plugin. These plugins may use + GTK+ code, but may not use window toolkit code, such as + X11, Win32, Cocoa, or Carbon. + - gtk-x11 - A GTK+ 2.x plugin that uses X11 code. These plugins may + use both GTK+ code and X11 code, allowing to hook into + features specific to X11. + - gtk-win32 - A GTK+ plugin that uses Win32 code. These plugins may use + both GTK+ code and Win32 code, allowing to hook into + features available on Windows. + - gnt - A GNT (a.k.a. Finch) plugin. These plugins may use GNT code. + - qpe - A plugin for the (now-abandoned) Qutopia user interface. + + The @em username must be a unique identifier for you. It + @em should be your http://developer.pidgin.im Trac user ID. Failing that, you + could use your SourceForge user ID or your Freenode IRC nickname, if you + have either. The http://developer.pidgin.im Trac user ID is preferred. + Do @em not leave this field blank! + + The @em pluginname is the name of your plugin. It is usually all + lowercase letters and matches the static plugin ID (the first argument to + the PURPLE_INIT_PLUGIN() macro call), although it can be anything you + like. Do @em not include version information in the plugin ID--the + #PurplePluginInfo structure already has a field for this. + + @section nospaces One Last Rule for Plugin IDs + + The last rule of plugin IDs is the most important of all. Plugin IDs may + @em NOT contain spaces. If you need a space, use another hyphen (-). - The @em username must be a unique identifier for that person. It - @em should be your SourceForge ID. Do @em not leave this field - blank. + @section exceptions Exceptions to the Rule + + As with any rule there are exceptions. If you browse through the source + tree you will see that the plugins we distribute with the Pidgin source + do not contain a username field. This is because while one developer may + have written each specific plugin, the plugins are maintained + collectively by the entire development team. This lack of a username + field is also an indicator that the plugin is one of our plugins and not + a third-party plugin. + + Another exception to the rule is the <a + href="http://plugins.guifications.org/trac/wiki/PluginPack">Purple Plugin + Pack</a>. All plugins whose lives started in the Purple Plugin Pack use + <tt>"plugin_pack"</tt> for the username field to indicate origination in + the Purple Plugin Pack. - The @em pluginname is the name of your plugin. It can be whatever you like, - though it's common to keep it all lowercase. Do not use spaces! If you - want a space, use a '-'. Please do not put a version indicator in the ID. - The PurplePlugin structure already has a field for this. + These two exceptions are mentioned here for completeness. We don't + encourage breaking the conventions set forth by the rules outlined above. + + @section examples Examples of Well-Chosen Plugin IDs + + The following is a list of well-chosen Plugin IDs listing a few good examples. + - <tt>"gtk-amc_grim-guifications"</tt> - This is the plugin ID for the + Guifications 2.x plugin. + - <tt>"gtk-rlaager-album"</tt> - This is the plugin ID for the Album + plugin, which is now part of the + Purple Plugin Pack. Its ID follows the + rules because its life started prior + to its inclusion in the Plugin Pack. + - <tt>"core-rlaager-irchelper"</tt> - This is the plugin ID for the IRC + Helper plugin, which is now part + of the Purple Plugin Pack. Its ID + follows the rules because its + life started prior to its + inclusion in the Plugin Pack. @section plugin-db Plugin Database Although it doesn't exist yet, in time there will be a plugin database @@ -45,4 +97,4 @@ */ -// vim: syntax=c tw=75 et +// vim: syntax=c.doxygen tw=75 et
--- a/doc/plugin-signals.dox Sat Nov 17 02:19:52 2007 +0000 +++ b/doc/plugin-signals.dox Sat Nov 17 02:20:01 2007 +0000 @@ -5,6 +5,8 @@ @signal plugin-unload @endsignals + @see plugin.h + <hr> @signaldef plugin-load @@ -26,4 +28,4 @@ @endsignaldef */ -// vim: syntax=c tw=75 et +// vim: syntax=c.doxygen tw=75 et
--- a/doc/savedstatus-signals.dox Sat Nov 17 02:19:52 2007 +0000 +++ b/doc/savedstatus-signals.dox Sat Nov 17 02:20:01 2007 +0000 @@ -4,6 +4,8 @@ @signal savedstatus-changed @endsignals + @see savedstatus.h + <hr> @signaldef savedstatus-changed @@ -15,4 +17,4 @@ @endsignaldef */ -// vim: syntax=c tw=75 et +// vim: syntax=c.doxygen tw=75 et
--- a/doc/sound-signals.dox Sat Nov 17 02:19:52 2007 +0000 +++ b/doc/sound-signals.dox Sat Nov 17 02:20:01 2007 +0000 @@ -4,6 +4,8 @@ @signal playing-sound-event @endsignals + @see sound.h + <hr> @signaldef playing-sound-event @@ -18,4 +20,4 @@ @endsignaldef */ -// vim: syntax=c tw=75 et +// vim: syntax=c.doxygen tw=75 et
--- a/doc/xfer-signals.dox Sat Nov 17 02:19:52 2007 +0000 +++ b/doc/xfer-signals.dox Sat Nov 17 02:20:01 2007 +0000 @@ -12,6 +12,8 @@ @signal file-send-complete @endsignals + @see ft.h + <hr> @signaldef file-recv-accept @@ -109,4 +111,4 @@ @endsignaldef */ -// vim: syntax=c tw=75 et +// vim: syntax=c.doxygen tw=75 et
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doxy2devhelp.xsl Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,98 @@ +<xsl:stylesheet + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:fo="http://www.w3.org/1999/XSL/Format" + version="1.0"> + +<!-- Based on http://bur.st/~eleusis/devhelp/doxy2devhelp.xsl + (http://bur.st/~eleusis/devhelp/README) + which is based on http://bugzilla.gnome.org/show_bug.cgi?id=122450 +--> + +<xsl:output method="xml" version="1.0" indent="yes"/> + +<xsl:param name="reference_prefix"></xsl:param> + +<xsl:template match="/"> + <book title="Pidgin Documentation" + name="pidgin" + link="{$reference_prefix}main.html"> + <chapters> + <sub name="Modules" link="{$reference_prefix}modules.html"> + <xsl:apply-templates select="doxygenindex/compound[@kind='group']"> + <xsl:sort select="."/> + </xsl:apply-templates> + </sub> + <!-- annotated.html has the short descriptions beside each struct. is + that more useful than being grouped alphabetically? + --> + <sub name="Structs" link="{$reference_prefix}classes.html"> + <xsl:apply-templates select="doxygenindex/compound[@kind='struct']"> + <xsl:sort select="."/> + </xsl:apply-templates> + </sub> + <!-- This is redundant given Modules --> + <!-- + <sub name="Directories" link="{$reference_prefix}dirs.html"> + <xsl:apply-templates select="doxygenindex/compound[@kind='dir']"> + <xsl:sort select="."/> + </xsl:apply-templates> + </sub> + --> + <!-- FIXME: Some files show up here but are broken links; mostly + files that are under pages... + --> + <sub name="Files" link="{$reference_prefix}files.html"> + <xsl:apply-templates select="doxygenindex/compound[@kind='file']"> + <xsl:sort select="."/> + </xsl:apply-templates> + </sub> + <sub name="Signals, HOWTOs, Other" link="{$reference_prefix}pages.html"> + <xsl:apply-templates select="doxygenindex/compound[@kind='page']"> + <xsl:sort select="."/> + </xsl:apply-templates> + </sub> + </chapters> + + <functions> + <!-- @todo: maybe select only the real functions, ie those with kind=="function"? --> + <xsl:apply-templates select="doxygenindex/compound/member" mode="as-function"/> + </functions> + </book> +</xsl:template> + +<xsl:template match="compound"> + <xsl:param name="name"><xsl:value-of select="name"/></xsl:param> + <xsl:param name="link"><xsl:value-of select="@refid"/>.html</xsl:param> + <sub name="{$name}" link="{$reference_prefix}{$link}"> + <xsl:apply-templates select="member" mode="as-sub"> + <xsl:sort select="."/> + </xsl:apply-templates> + </sub> +</xsl:template> + +<xsl:template match="member" mode="as-function"> + <!-- + <function name="atk_set_value" link="atk-atkvalue.html#ATK-SET-VALUE"/> + --> + <xsl:param name="name"><xsl:value-of select="name"/></xsl:param> + <!-- Link is refid attribute of parent element + "#" + diff between refid of parent and own refid --> + <xsl:param name="refid_parent"><xsl:value-of select="parent::node()/@refid"/></xsl:param> + <xsl:param name="own_refid"><xsl:value-of select="@refid"/></xsl:param> + <xsl:param name="offset"><xsl:value-of select="string-length($refid_parent) + 3"/></xsl:param> + <xsl:param name="ref_diff"><xsl:value-of select="substring($own_refid, $offset, 33)"/></xsl:param> + <xsl:param name="link"><xsl:value-of select="$refid_parent"/>.html#<xsl:value-of select="$ref_diff"/></xsl:param> + <function name="{$name}" link="{$reference_prefix}{$link}"/> +</xsl:template> + +<xsl:template match="member" mode="as-sub"> + <xsl:param name="name"><xsl:value-of select="name"/></xsl:param> + <!-- Link is refid attribute of parent element + "#" + diff between refid of parent and own refid --> + <xsl:param name="refid_parent"><xsl:value-of select="parent::node()/@refid"/></xsl:param> + <xsl:param name="own_refid"><xsl:value-of select="@refid"/></xsl:param> + <xsl:param name="offset"><xsl:value-of select="string-length($refid_parent) + 3"/></xsl:param> + <xsl:param name="ref_diff"><xsl:value-of select="substring($own_refid, $offset, 33)"/></xsl:param> + <xsl:param name="link"><xsl:value-of select="$refid_parent"/>.html#<xsl:value-of select="$ref_diff"/></xsl:param> + <sub name="{$name}" link="{$reference_prefix}{$link}"/> +</xsl:template> + +</xsl:stylesheet>
--- a/finch/Makefile.am Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/Makefile.am Sat Nov 17 02:20:01 2007 +0000 @@ -1,7 +1,11 @@ EXTRA_DIST = \ getopt.c \ getopt.h \ - getopt1.c + getopt1.c \ + finch.pc.in + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = finch.pc SUBDIRS = libgnt plugins
--- a/finch/finch.c Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/finch.c Sat Nov 17 02:20:01 2007 +0000 @@ -203,7 +203,7 @@ char *text; if (terse) { - text = g_strdup_printf(_("%s. Try `%s -h' for more information.\n"), VERSION, name); + text = g_strdup_printf(_("%s. Try `%s -h' for more information.\n"), DISPLAY_VERSION, name); } else { text = g_strdup_printf(_("%s\n" "Usage: %s [OPTION]...\n\n" @@ -211,7 +211,7 @@ " -d, --debug print debugging messages to stdout\n" " -h, --help display this help and exit\n" " -n, --nologin don't automatically login\n" - " -v, --version display the current version and exit\n"), VERSION, name); + " -v, --version display the current version and exit\n"), DISPLAY_VERSION, name); } purple_print_utf8_to_console(stdout, text); @@ -298,7 +298,7 @@ /* Translators may want to transliterate the name. It is not to be translated. */ gnt_quit(); - printf("%s %s\n", _("Finch"), VERSION); + printf("%s %s\n", _("Finch"), DISPLAY_VERSION); return 0; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/finch/finch.pc.in Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,14 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ +datarootdir=@datarootdir@ +datadir=@datadir@ +sysconfdir=@sysconfdir@ + +Name: Finch +Description: Finch is an instant messenger application that uses libpurple for protocol support and ncurses (libgnt) for the UI. +Version: @VERSION@ +Requires: gnt purple +Cflags: -I${includedir}/finch +
--- a/finch/gntaccount.h Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/gntaccount.h Sat Nov 17 02:20:01 2007 +0000 @@ -59,6 +59,8 @@ * Show the edit dialog for an account. * * @param account The account to edit, or @c NULL to create a new account. + * + * @since 2.2.0 */ void finch_account_dialog_show(PurpleAccount *account);
--- a/finch/gntblist.c Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/gntblist.c Sat Nov 17 02:20:01 2007 +0000 @@ -1506,6 +1506,8 @@ { if (text[0] == 27 && text[1] == 0) { /* Escape was pressed */ + if (gnt_tree_is_searching(GNT_TREE(ggblist->tree))) + gnt_bindable_perform_action_named(GNT_BINDABLE(ggblist->tree), "end-search", NULL); remove_peripherals(ggblist); } else if (strcmp(text, GNT_KEY_CTRL_O) == 0) { purple_prefs_set_bool(PREF_ROOT "/showoffline",
--- a/finch/gntblist.h Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/gntblist.h Sat Nov 17 02:20:01 2007 +0000 @@ -98,6 +98,8 @@ * @param name The user to get information about. * * @return Returns the ui-handle for the userinfo notification. + * + * @since 2.1.0 */ gpointer finch_retrieve_user_info(PurpleConnection *conn, const char *name);
--- a/finch/gntconv.c Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/gntconv.c Sat Nov 17 02:20:01 2007 +0000 @@ -114,87 +114,81 @@ } } -static gboolean -entry_key_pressed(GntWidget *w, const char *key, FinchConv *ggconv) +static void +entry_key_pressed(GntWidget *w, FinchConv *ggconv) { - if (key[0] == '\r' && key[1] == 0) + const char *text = gnt_entry_get_text(GNT_ENTRY(ggconv->entry)); + if (*text == '/') { - const char *text = gnt_entry_get_text(GNT_ENTRY(ggconv->entry)); - if (*text == '/') - { - PurpleConversation *conv = ggconv->active_conv; - PurpleCmdStatus status; - const char *cmdline = text + 1; - char *error = NULL, *escape; + PurpleConversation *conv = ggconv->active_conv; + PurpleCmdStatus status; + const char *cmdline = text + 1; + char *error = NULL, *escape; + + escape = g_markup_escape_text(cmdline, -1); + status = purple_cmd_do_command(conv, cmdline, escape, &error); + g_free(escape); - escape = g_markup_escape_text(cmdline, -1); - status = purple_cmd_do_command(conv, cmdline, escape, &error); - g_free(escape); - - switch (status) - { - case PURPLE_CMD_STATUS_OK: - break; - case PURPLE_CMD_STATUS_NOT_FOUND: - purple_conversation_write(conv, "", _("No such command."), - PURPLE_MESSAGE_NO_LOG, time(NULL)); - break; - case PURPLE_CMD_STATUS_WRONG_ARGS: - purple_conversation_write(conv, "", _("Syntax Error: You typed the wrong number of arguments " - "to that command."), - PURPLE_MESSAGE_NO_LOG, time(NULL)); - break; - case PURPLE_CMD_STATUS_FAILED: - purple_conversation_write(conv, "", error ? error : _("Your command failed for an unknown reason."), + switch (status) + { + case PURPLE_CMD_STATUS_OK: + break; + case PURPLE_CMD_STATUS_NOT_FOUND: + purple_conversation_write(conv, "", _("No such command."), + PURPLE_MESSAGE_NO_LOG, time(NULL)); + break; + case PURPLE_CMD_STATUS_WRONG_ARGS: + purple_conversation_write(conv, "", _("Syntax Error: You typed the wrong number of arguments " + "to that command."), + PURPLE_MESSAGE_NO_LOG, time(NULL)); + break; + case PURPLE_CMD_STATUS_FAILED: + purple_conversation_write(conv, "", error ? error : _("Your command failed for an unknown reason."), + PURPLE_MESSAGE_NO_LOG, time(NULL)); + break; + case PURPLE_CMD_STATUS_WRONG_TYPE: + if(purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) + purple_conversation_write(conv, "", _("That command only works in chats, not IMs."), PURPLE_MESSAGE_NO_LOG, time(NULL)); - break; - case PURPLE_CMD_STATUS_WRONG_TYPE: - if(purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) - purple_conversation_write(conv, "", _("That command only works in chats, not IMs."), - PURPLE_MESSAGE_NO_LOG, time(NULL)); - else - purple_conversation_write(conv, "", _("That command only works in IMs, not chats."), - PURPLE_MESSAGE_NO_LOG, time(NULL)); - break; - case PURPLE_CMD_STATUS_WRONG_PRPL: - purple_conversation_write(conv, "", _("That command doesn't work on this protocol."), + else + purple_conversation_write(conv, "", _("That command only works in IMs, not chats."), PURPLE_MESSAGE_NO_LOG, time(NULL)); - break; - } - g_free(error); + break; + case PURPLE_CMD_STATUS_WRONG_PRPL: + purple_conversation_write(conv, "", _("That command doesn't work on this protocol."), + PURPLE_MESSAGE_NO_LOG, time(NULL)); + break; } - 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 + 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); + char *apos = purple_strreplace(escape, "'", "'"); + g_free(escape); + escape = apos; + switch (purple_conversation_get_type(ggconv->active_conv)) { - char *escape = g_markup_escape_text(text, -1); - char *apos = purple_strreplace(escape, "'", "'"); - g_free(escape); - escape = apos; - switch (purple_conversation_get_type(ggconv->active_conv)) - { - case PURPLE_CONV_TYPE_IM: - purple_conv_im_send_with_flags(PURPLE_CONV_IM(ggconv->active_conv), escape, PURPLE_MESSAGE_SEND); - break; - case PURPLE_CONV_TYPE_CHAT: - purple_conv_chat_send(PURPLE_CONV_CHAT(ggconv->active_conv), escape); - break; - default: - g_free(escape); - g_return_val_if_reached(FALSE); - } - g_free(escape); - purple_idle_touch(); + case PURPLE_CONV_TYPE_IM: + purple_conv_im_send_with_flags(PURPLE_CONV_IM(ggconv->active_conv), escape, PURPLE_MESSAGE_SEND); + break; + case PURPLE_CONV_TYPE_CHAT: + purple_conv_chat_send(PURPLE_CONV_CHAT(ggconv->active_conv), escape); + break; + default: + g_free(escape); + g_return_if_reached(); } - gnt_entry_add_to_history(GNT_ENTRY(ggconv->entry), text); - gnt_entry_clear(GNT_ENTRY(ggconv->entry)); - return TRUE; + g_free(escape); + purple_idle_touch(); } - - return FALSE; + gnt_entry_add_to_history(GNT_ENTRY(ggconv->entry), text); + gnt_entry_clear(GNT_ENTRY(ggconv->entry)); } static void @@ -689,7 +683,7 @@ gnt_text_view_attach_scroll_widget(GNT_TEXT_VIEW(ggc->tv), ggc->entry); gnt_text_view_attach_pager_widget(GNT_TEXT_VIEW(ggc->tv), ggc->entry); - g_signal_connect_after(G_OBJECT(ggc->entry), "key_pressed", G_CALLBACK(entry_key_pressed), ggc); + g_signal_connect_after(G_OBJECT(ggc->entry), "activate", G_CALLBACK(entry_key_pressed), ggc); g_signal_connect(G_OBJECT(ggc->entry), "completion", G_CALLBACK(completion_cb), NULL); g_signal_connect(G_OBJECT(ggc->window), "destroy", G_CALLBACK(closing_window), ggc); @@ -1039,7 +1033,7 @@ PurpleCmdStatus status; if (!g_ascii_strcasecmp(args[0], "version")) { - tmp = g_strdup_printf("me is using Finch v%s.", VERSION); + tmp = g_strdup_printf("me is using Finch v%s.", DISPLAY_VERSION); markup = g_markup_escape_text(tmp, -1); status = purple_cmd_do_command(conv, tmp, markup, error);
--- a/finch/gntnotify.c Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/gntnotify.c Sat Nov 17 02:20:01 2007 +0000 @@ -84,8 +84,8 @@ if (type == PURPLE_NOTIFY_FORMATTED) { int width = -1, height = -1; msg = gnt_text_view_new(); + gnt_text_view_set_flag(GNT_TEXT_VIEW(msg), GNT_TEXT_VIEW_TOP_ALIGN); gnt_text_view_append_text_with_flags(GNT_TEXT_VIEW(msg), secondary, sf); - gnt_text_view_scroll(GNT_TEXT_VIEW(msg), 0); gnt_text_view_attach_scroll_widget(GNT_TEXT_VIEW(msg), button); gnt_util_get_text_bound(secondary, &width, &height); gnt_widget_set_size(msg, width + 3, height + 1); @@ -194,6 +194,7 @@ PurpleAccount *account = purple_connection_get_account(gc); GString *message = g_string_new(NULL); void *ret; + static int key = 0; if (!detailed) { @@ -212,7 +213,7 @@ to = g_strdup_printf("%s (%s)", tos ? *tos : purple_account_get_username(account), purple_account_get_protocol_name(account)); - gnt_tree_add_row_after(GNT_TREE(emaildialog.tree), GINT_TO_POINTER(time(NULL)), + gnt_tree_add_row_after(GNT_TREE(emaildialog.tree), GINT_TO_POINTER(++key), gnt_tree_create_row(GNT_TREE(emaildialog.tree), to, froms ? *froms : "[Unknown sender]", *subjects), @@ -360,7 +361,8 @@ i = 0; for (iter = results->columns; iter; iter = iter->next) { - gnt_tree_set_column_title(GNT_TREE(tree), i, iter->data); + PurpleNotifySearchColumn *column = iter->data; + gnt_tree_set_column_title(GNT_TREE(tree), i, column->title); i++; }
--- a/finch/gntplugin.c Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/gntplugin.c Sat Nov 17 02:20:01 2007 +0000 @@ -29,6 +29,7 @@ #include <gntlabel.h> #include <gntline.h> #include <gnttree.h> +#include <gntutils.h> #include "finch.h" @@ -83,6 +84,7 @@ if (!purple_plugin_unload(plugin)) { purple_notify_error(NULL, _("ERROR"), _("unloading plugin failed"), NULL); + purple_plugin_disable(plugin); gnt_tree_set_choice(GNT_TREE(tree), plugin, TRUE); } @@ -398,6 +400,7 @@ button = gnt_button_new(_("Install Plugin...")); gnt_box_add_widget(GNT_BOX(box), button); + gnt_util_set_trigger_widget(GNT_WIDGET(tree), GNT_KEY_INS, button); g_signal_connect(G_OBJECT(button), "activate", G_CALLBACK(install_plugin_cb), NULL); button = gnt_button_new(_("Close"));
--- a/finch/gntpounce.c Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/gntpounce.c Sat Nov 17 02:20:01 2007 +0000 @@ -168,6 +168,17 @@ } static void +setup_buddy_list_suggestion(GntEntry *entry, gboolean offline) +{ + PurpleBlistNode *node = purple_blist_get_root(); + for (; node; node = purple_blist_node_next(node, offline)) { + if (!PURPLE_BLIST_NODE_IS_BUDDY(node)) + continue; + gnt_entry_add_suggest(entry, purple_buddy_get_name((PurpleBuddy*)node)); + } +} + +static void save_pounce_cb(GntWidget *w, PurpleGntPounceDialog *dialog) { const char *name; @@ -360,6 +371,8 @@ dialog->buddy_entry = gnt_entry_new(NULL); gnt_box_add_widget(GNT_BOX(hbox), dialog->buddy_entry); + setup_buddy_list_suggestion(GNT_ENTRY(dialog->buddy_entry), TRUE); + gnt_box_add_widget(GNT_BOX(window), hbox); if (cur_pounce != NULL) {
--- a/finch/gntsound.h Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/gntsound.h Sat Nov 17 02:20:01 2007 +0000 @@ -37,6 +37,8 @@ * Get the name of the active sound profile. * * @return The name of the profile + * + * @since 2.1.0 */ const char *finch_sound_get_active_profile(void); @@ -44,6 +46,8 @@ * Set the active profile. If the profile doesn't exist, nothing is changed. * * @param name The name of the profile + * + * @since 2.1.0 */ void finch_sound_set_active_profile(const char *name); @@ -52,6 +56,8 @@ * * @return A list of strings denoting sound profile names. * Caller must free the list (but not the data). + * + * @since 2.1.0 */ GList *finch_sound_get_profiles(void); @@ -60,6 +66,8 @@ * * @return Returns FALSE if preference is set to 'No sound', or if volume is * set to zero. + * + * @since 2.2.0 */ gboolean finch_sound_is_enabled(void); @@ -67,11 +75,15 @@ * Gets GNT sound UI ops. * * @return The UI operations structure. + * + * @since 2.1.0 */ PurpleSoundUiOps *finch_sound_get_ui_ops(void); /** * Show the sound settings dialog. + * + * @since 2.1.0 */ void finch_sounds_show_all(void);
--- a/finch/libgnt/gnt.h Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gnt.h Sat Nov 17 02:20:01 2007 +0000 @@ -46,123 +46,158 @@ #endif /** - * + * Initialize GNT. */ void gnt_init(void); /** - * + * Start running the mainloop for gnt. */ void gnt_main(void); /** - * + * Check whether the terminal is capable of UTF8 display. * - * @return + * @return @c FALSE if the terminal is capable of drawing UTF-8, @c TRUE otherwise. */ gboolean gnt_ascii_only(void); -void gnt_window_present(GntWidget *window); /** - * - * @param widget + * Present a window. If the event was triggered because of user interaction, + * the window is moved to the foreground. Otherwise, the Urgent hint is set. + * + * @param window The window the present. + * + * @since 2.0.0 (gnt), 2.1.0 (pidgin) + */ +void gnt_window_present(GntWidget *window); + +/** + * @internal + * Use #gnt_widget_show instead. */ void gnt_screen_occupy(GntWidget *widget); /** - * - * @param widget + * @internal + * Use #gnt_widget_hide instead. */ void gnt_screen_release(GntWidget *widget); /** - * - * @param widget + * @internal + * Use #gnt_widget_draw instead. */ void gnt_screen_update(GntWidget *widget); /** - * - * @param widget - * @param width - * @param height + * Resize a widget. + * + * @param widget The widget to resize. + * @param width The desired width. + * @param height The desired height. */ void gnt_screen_resize_widget(GntWidget *widget, int width, int height); /** - * - * @param widget - * @param x - * @param y + * Move a widget. + * + * @param widget The widget to move. + * @param x The desired x-coordinate. + * @param y The desired y-coordinate. */ void gnt_screen_move_widget(GntWidget *widget, int x, int y); /** - * - * @param widget - * @param text + * Rename a widget. + * + * @param widget The widget to rename. + * @param text The new name for the widget. */ void gnt_screen_rename_widget(GntWidget *widget, const char *text); /** - * - * @param widget + * Check whether a widget has focus. * - * @return + * @param widget The widget. + * + * @return @c TRUE if the widget has the current focus, @c FALSE otherwise. */ gboolean gnt_widget_has_focus(GntWidget *widget); /** - * - * @param widget + * Set the URGENT hint for a widget. + * + * @param widget The widget to set the URGENT hint for. */ void gnt_widget_set_urgent(GntWidget *widget); /** - * - * @param label - * @param callback + * Register a global action. + * + * @param label The user-visible label for the action. + * @param callback The callback function for the action. */ void gnt_register_action(const char *label, void (*callback)()); /** - * - * @param menu + * Show a menu. * - * @return + * @param menu The menu to display. + * + * @return @c TRUE if the menu is displayed, @c FALSE otherwise (e.g., if another menu is currently displayed). */ gboolean gnt_screen_menu_show(gpointer menu); /** - * + * Terminate the mainloop of gnt. */ void gnt_quit(void); /** - * + * Get the global clipboard. * - * @return + * @return The clipboard. */ GntClipboard * gnt_get_clipboard(void); /** - * + * Get the string in the clipboard. * - * @return + * @return A copy of the string in the clipboard. The caller must @c g_free the string. */ gchar * gnt_get_clipboard_string(void); /** - * - * @param string + * Set the contents of the global clipboard. + * + * @param string The new content of the new clipboard. */ -void gnt_set_clipboard_string(gchar *string); +void gnt_set_clipboard_string(const gchar *string); /** * Spawn a different application that will consume the console. + * + * @param wd The working directory for the new application. + * @param argv The argument vector. + * @param envp The environment, or @c NULL. + * @param stin Location to store the child's stdin, or @c NULL. + * @param stout Location to store the child's stdout, or @c NULL. + * @param sterr Location to store the child's stderr, or @c NULL. + * @param callback The callback to call after the child exits. + * @param data The data to pass to the callback. + * + * @return @c TRUE if the child was successfully spawned, @c FALSE otherwise. */ gboolean gnt_giveup_console(const char *wd, char **argv, char **envp, gint *stin, gint *stout, gint *sterr, void (*callback)(int status, gpointer data), gpointer data); +/** + * Check whether a child process is in control of the current terminal. + * + * @return @c TRUE if a child process (eg., PAGER) is occupying the current + * terminal, @c FALSE otherwise. + */ gboolean gnt_is_refugee(void); +
--- a/finch/libgnt/gntbindable.h Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gntbindable.h Sat Nov 17 02:20:01 2007 +0000 @@ -160,8 +160,8 @@ /** * Returns a GntTree populated with "key" -> "binding" for the widget. - * - * @param widget The object to list the bindings for. + * + * @param bind The object to list the bindings for. * * @return The GntTree. */ @@ -170,9 +170,9 @@ /** * Builds a window that list the key bindings for a GntBindable object. * From this window a user can select a listing to rebind a new key for the given action. - * + * * @param bindable The object to list the bindings for. - * + * * @return @c TRUE */
--- a/finch/libgnt/gntbox.c Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gntbox.c Sat Nov 17 02:20:01 2007 +0000 @@ -27,6 +27,13 @@ enum { + PROP_0, + PROP_VERTICAL, + PROP_HOMO /* ... */ +}; + +enum +{ SIGS = 1, }; @@ -80,12 +87,12 @@ get_title_thingies(box, title, &pos, &right); if (gnt_widget_has_focus(widget)) - wbkgdset(widget->window, '\0' | COLOR_PAIR(GNT_COLOR_TITLE)); + wbkgdset(widget->window, '\0' | gnt_color_pair(GNT_COLOR_TITLE)); else - wbkgdset(widget->window, '\0' | COLOR_PAIR(GNT_COLOR_TITLE_D)); - mvwaddch(widget->window, 0, pos-1, ACS_RTEE | COLOR_PAIR(GNT_COLOR_NORMAL)); + wbkgdset(widget->window, '\0' | gnt_color_pair(GNT_COLOR_TITLE_D)); + mvwaddch(widget->window, 0, pos-1, ACS_RTEE | gnt_color_pair(GNT_COLOR_NORMAL)); mvwaddstr(widget->window, 0, pos, title); - mvwaddch(widget->window, 0, right, ACS_LTEE | COLOR_PAIR(GNT_COLOR_NORMAL)); + mvwaddch(widget->window, 0, right, ACS_LTEE | gnt_color_pair(GNT_COLOR_NORMAL)); g_free(title); } @@ -511,8 +518,44 @@ } static void +gnt_box_set_property(GObject *obj, guint prop_id, const GValue *value, + GParamSpec *spec) +{ + GntBox *box = GNT_BOX(obj); + switch (prop_id) { + case PROP_VERTICAL: + box->vertical = g_value_get_boolean(value); + break; + case PROP_HOMO: + box->homogeneous = g_value_get_boolean(value); + break; + default: + g_return_if_reached(); + break; + } +} + +static void +gnt_box_get_property(GObject *obj, guint prop_id, GValue *value, + GParamSpec *spec) +{ + GntBox *box = GNT_BOX(obj); + switch (prop_id) { + case PROP_VERTICAL: + g_value_set_boolean(value, box->vertical); + break; + case PROP_HOMO: + g_value_set_boolean(value, box->homogeneous); + break; + default: + break; + } +} + +static void gnt_box_class_init(GntBoxClass *klass) { + GObjectClass *gclass = G_OBJECT_CLASS(klass); parent_class = GNT_WIDGET_CLASS(klass); parent_class->destroy = gnt_box_destroy; parent_class->draw = gnt_box_draw; @@ -527,7 +570,24 @@ parent_class->confirm_size = gnt_box_confirm_size; parent_class->size_changed = gnt_box_size_changed; - GNTDEBUG; + gclass->set_property = gnt_box_set_property; + gclass->get_property = gnt_box_get_property; + g_object_class_install_property(gclass, + PROP_VERTICAL, + g_param_spec_boolean("vertical", "Vertical", + "Whether the child widgets in the box should be stacked vertically.", + TRUE, + G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB + ) + ); + g_object_class_install_property(gclass, + PROP_HOMO, + g_param_spec_boolean("homogeneous", "Homogeneous", + "Whether the child widgets in the box should have the same size.", + TRUE, + G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB + ) + ); } static void @@ -603,7 +663,7 @@ /* Erase the old title */ int pos, right; get_title_thingies(b, prev, &pos, &right); - mvwhline(w->window, 0, pos - 1, ACS_HLINE | COLOR_PAIR(GNT_COLOR_NORMAL), + mvwhline(w->window, 0, pos - 1, ACS_HLINE | gnt_color_pair(GNT_COLOR_NORMAL), right - pos + 2); g_free(prev); }
--- a/finch/libgnt/gntbutton.c Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gntbutton.c Sat Nov 17 02:20:01 2007 +0000 @@ -47,7 +47,7 @@ else type = GNT_COLOR_NORMAL; - wbkgdset(widget->window, '\0' | COLOR_PAIR(type)); + wbkgdset(widget->window, '\0' | gnt_color_pair(type)); mvwaddstr(widget->window, (small_button) ? 0 : 1, 2, button->priv->text); if (small_button) { type = GNT_COLOR_HIGHLIGHT; @@ -98,6 +98,14 @@ } static void +gnt_button_destroy(GntWidget *widget) +{ + GntButton *button = GNT_BUTTON(widget); + g_free(button->priv->text); + g_free(button->priv); +} + +static void gnt_button_class_init(GntWidgetClass *klass) { char *style; @@ -108,6 +116,7 @@ parent_class->size_request = gnt_button_size_request; parent_class->key_pressed = gnt_button_key_pressed; parent_class->clicked = gnt_button_clicked; + parent_class->destroy = gnt_button_destroy; style = gnt_style_get_from_name(NULL, "small-button"); small_button = gnt_style_parse_bool(style); @@ -126,6 +135,7 @@ widget->priv.minh = small_button ? 1 : 3; if (small_button) GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_NO_BORDER | GNT_WIDGET_NO_SHADOW); + GNT_WIDGET_UNSET_FLAGS(widget, GNT_WIDGET_GROW_X | GNT_WIDGET_GROW_Y); GNTDEBUG; }
--- a/finch/libgnt/gntcheckbox.c Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gntcheckbox.c Sat Nov 17 02:20:01 2007 +0000 @@ -43,13 +43,13 @@ else type = GNT_COLOR_NORMAL; - wbkgdset(widget->window, '\0' | COLOR_PAIR(type)); + wbkgdset(widget->window, '\0' | gnt_color_pair(type)); text = g_strdup_printf("[%c]", cb->checked ? 'X' : ' '); mvwaddstr(widget->window, 0, 0, text); g_free(text); - wbkgdset(widget->window, '\0' | COLOR_PAIR(GNT_COLOR_NORMAL)); + wbkgdset(widget->window, '\0' | gnt_color_pair(GNT_COLOR_NORMAL)); mvwaddstr(widget->window, 0, 4, GNT_BUTTON(cb)->priv->text); GNTDEBUG;
--- a/finch/libgnt/gntcolors.c Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gntcolors.c Sat Nov 17 02:20:01 2007 +0000 @@ -32,6 +32,7 @@ #include <stdlib.h> #include <string.h> +static gboolean hascolors; static struct { short r, g, b; @@ -75,6 +76,8 @@ init = TRUE; start_color(); + if (!(hascolors = has_colors())) + return; defaults = use_default_colors(); if (can_use_custom_color()) @@ -276,3 +279,11 @@ } #endif /* GKeyFile */ + +int gnt_color_pair(int pair) +{ + return (hascolors ? COLOR_PAIR(pair) : + ((pair == GNT_COLOR_NORMAL || pair == GNT_COLOR_HIGHLIGHT_D || + pair == GNT_COLOR_TITLE_D || pair == GNT_COLOR_DISABLED) ? 0 : A_STANDOUT)); +} +
--- a/finch/libgnt/gntcolors.h Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gntcolors.h Sat Nov 17 02:20:01 2007 +0000 @@ -88,4 +88,17 @@ #endif +/** + * Return the appropriate character attribute for a specified color. + * If the terminal doesn't have color support, this returns A_STANDOUT + * when deemed appropriate. + * + * @param color The color code. + * + * @return A character attribute. + * + * @since 2.3.0 + */ +int gnt_color_pair(int color); + #endif
--- a/finch/libgnt/gntcombobox.c Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gntcombobox.c Sat Nov 17 02:20:01 2007 +0000 @@ -85,15 +85,15 @@ else type = GNT_COLOR_NORMAL; - wbkgdset(widget->window, '\0' | COLOR_PAIR(type)); + wbkgdset(widget->window, '\0' | gnt_color_pair(type)); s = (char*)gnt_util_onscreen_width_to_pointer(text, widget->priv.width - 4, &len); *s = '\0'; mvwaddstr(widget->window, 1, 1, text); - whline(widget->window, ' ' | COLOR_PAIR(type), widget->priv.width - 4 - len); - mvwaddch(widget->window, 1, widget->priv.width - 3, ACS_VLINE | COLOR_PAIR(GNT_COLOR_NORMAL)); - mvwaddch(widget->window, 1, widget->priv.width - 2, ACS_DARROW | COLOR_PAIR(GNT_COLOR_NORMAL)); + whline(widget->window, ' ' | gnt_color_pair(type), widget->priv.width - 4 - len); + mvwaddch(widget->window, 1, widget->priv.width - 3, ACS_VLINE | gnt_color_pair(GNT_COLOR_NORMAL)); + mvwaddch(widget->window, 1, widget->priv.width - 2, ACS_DARROW | gnt_color_pair(GNT_COLOR_NORMAL)); g_free(text); GNTDEBUG;
--- a/finch/libgnt/gntentry.c Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gntentry.c Sat Nov 17 02:20:01 2007 +0000 @@ -36,6 +36,24 @@ SIG_COMPLETION, SIGS, }; + +typedef enum +{ + ENTRY_JAIL = -1, /* Suspend the kill ring. */ + ENTRY_DEL_BWD_WORD = 1, + ENTRY_DEL_BWD_CHAR, + ENTRY_DEL_FWD_WORD, + ENTRY_DEL_FWD_CHAR, + ENTRY_DEL_EOL, + ENTRY_DEL_BOL, +} GntEntryAction; + +struct _GntEntryKillRing +{ + GString *buffer; + GntEntryAction last; +}; + static guint signals[SIGS] = { 0 }; static GntWidgetClass *parent_class = NULL; @@ -43,6 +61,58 @@ static gboolean gnt_entry_key_pressed(GntWidget *widget, const char *text); static void gnt_entry_set_text_internal(GntEntry *entry, const char *text); +static gboolean +update_kill_ring(GntEntry *entry, GntEntryAction action, const char *text, int len) +{ + if (action < 0) { + entry->killring->last = action; + return FALSE; + } + + if (len == 0) + len = strlen(text); + else if (len < 0) { + text += len; + len = -len; + } + + if (action != entry->killring->last) { + struct { + GntEntryAction one; + GntEntryAction two; + } merges[] = { + {ENTRY_DEL_BWD_WORD, ENTRY_DEL_FWD_WORD}, + {ENTRY_DEL_BWD_CHAR, ENTRY_DEL_FWD_CHAR}, + {ENTRY_DEL_BOL, ENTRY_DEL_EOL}, + {ENTRY_JAIL, ENTRY_JAIL}, + }; + int i; + + for (i = 0; merges[i].one != ENTRY_JAIL; i++) { + if (merges[i].one == entry->killring->last && + merges[i].two == action) { + g_string_append_len(entry->killring->buffer, text, len); + break; + } else if (merges[i].one == action && + merges[i].two == entry->killring->last) { + g_string_prepend_len(entry->killring->buffer, text, len); + break; + } + } + if (merges[i].one == ENTRY_JAIL) { + g_string_assign(entry->killring->buffer, text); + g_string_truncate(entry->killring->buffer, len); + } + entry->killring->last = action; + } else { + if (action == ENTRY_DEL_BWD_CHAR || action == ENTRY_DEL_BWD_WORD) + g_string_prepend_len(entry->killring->buffer, text, len); + else + g_string_append_len(entry->killring->buffer, text, len); + } + return TRUE; +} + static void destroy_suggest(GntEntry *entry) { @@ -97,6 +167,7 @@ if (changed) g_signal_emit(G_OBJECT(entry), signals[SIG_COMPLETION], 0, entry->start + offstart, entry->start + offend); + update_kill_ring(entry, ENTRY_JAIL, NULL, 0); return changed; } @@ -201,9 +272,9 @@ gboolean focus; if ((focus = gnt_widget_has_focus(widget))) - wbkgdset(widget->window, '\0' | COLOR_PAIR(GNT_COLOR_TEXT_NORMAL)); + wbkgdset(widget->window, '\0' | gnt_color_pair(GNT_COLOR_TEXT_NORMAL)); else - wbkgdset(widget->window, '\0' | COLOR_PAIR(GNT_COLOR_HIGHLIGHT_D)); + wbkgdset(widget->window, '\0' | gnt_color_pair(GNT_COLOR_HIGHLIGHT_D)); if (entry->masked) { @@ -264,6 +335,7 @@ entry->cursor = g_utf8_find_prev_char(entry->start, entry->cursor); if (entry->cursor < entry->scroll) entry->scroll = entry->cursor; + update_kill_ring(entry, ENTRY_JAIL, NULL, 0); entry_redraw(GNT_WIDGET(entry)); return TRUE; } @@ -277,6 +349,7 @@ entry->cursor = g_utf8_find_next_char(entry->cursor, NULL); while (gnt_util_onscreen_width(entry->scroll, entry->cursor) >= GNT_WIDGET(entry)->priv.width) entry->scroll = g_utf8_find_next_char(entry->scroll, NULL); + update_kill_ring(entry, ENTRY_JAIL, NULL, 0); entry_redraw(GNT_WIDGET(entry)); return TRUE; } @@ -289,9 +362,11 @@ if (entry->cursor <= entry->start) return TRUE; - + len = entry->cursor - g_utf8_find_prev_char(entry->start, entry->cursor); + update_kill_ring(entry, ENTRY_DEL_BWD_CHAR, entry->cursor, -len); entry->cursor -= len; + memmove(entry->cursor, entry->cursor + len, entry->end - entry->cursor); entry->end -= len; @@ -313,8 +388,9 @@ if (entry->cursor >= entry->end) return FALSE; - + len = g_utf8_find_next_char(entry->cursor, NULL) - entry->cursor; + update_kill_ring(entry, ENTRY_DEL_FWD_CHAR, entry->cursor, len); memmove(entry->cursor, entry->cursor + len, entry->end - entry->cursor - len + 1); entry->end -= len; entry_redraw(GNT_WIDGET(entry)); @@ -331,6 +407,7 @@ GntEntry *entry = GNT_ENTRY(bind); entry->scroll = entry->cursor = entry->start; entry_redraw(GNT_WIDGET(entry)); + update_kill_ring(entry, ENTRY_JAIL, NULL, 0); return TRUE; } @@ -343,6 +420,7 @@ while (gnt_util_onscreen_width(entry->scroll, entry->cursor) >= GNT_WIDGET(entry)->priv.width) entry->scroll = g_utf8_find_next_char(entry->scroll, NULL); entry_redraw(GNT_WIDGET(entry)); + update_kill_ring(entry, ENTRY_JAIL, NULL, 0); return TRUE; } @@ -357,6 +435,7 @@ destroy_suggest(entry); entry_text_changed(entry); + update_kill_ring(entry, ENTRY_JAIL, NULL, 0); return TRUE; } return FALSE; @@ -381,6 +460,7 @@ destroy_suggest(entry); entry_text_changed(entry); + update_kill_ring(entry, ENTRY_JAIL, NULL, 0); return TRUE; } return FALSE; @@ -400,6 +480,7 @@ a = g_strndup(entry->start, entry->cursor - entry->start); all = g_strconcat(a, text, entry->cursor, NULL); gnt_entry_set_text_internal(entry, all); + update_kill_ring(entry, ENTRY_JAIL, NULL, 0); g_free(a); g_free(text); g_free(all); @@ -445,6 +526,7 @@ GntEntry *entry = GNT_ENTRY(bind); if (entry->cursor <= entry->start) return TRUE; + update_kill_ring(entry, ENTRY_DEL_BOL, entry->start, entry->cursor - entry->start); memmove(entry->start, entry->cursor, entry->end - entry->cursor); entry->end -= (entry->cursor - entry->start); entry->cursor = entry->scroll = entry->start; @@ -460,6 +542,7 @@ GntEntry *entry = GNT_ENTRY(bind); if (entry->end <= entry->cursor) return TRUE; + update_kill_ring(entry, ENTRY_DEL_EOL, entry->cursor, entry->end - entry->cursor); entry->end = entry->cursor; memset(entry->end, '\0', entry->buffer - (entry->end - entry->start)); entry_redraw(GNT_WIDGET(bind)); @@ -517,6 +600,7 @@ entry->cursor = (char*)iter; if (entry->cursor < entry->scroll) entry->scroll = entry->cursor; + update_kill_ring(entry, ENTRY_JAIL, NULL, 0); entry_redraw(GNT_WIDGET(bind)); return TRUE; } @@ -533,6 +617,7 @@ return TRUE; iter = (char*)begin_word(iter, entry->start); count = entry->cursor - iter; + update_kill_ring(entry, ENTRY_DEL_BWD_WORD, iter, count); memmove(iter, entry->cursor, entry->end - entry->cursor); entry->end -= count; entry->cursor = iter; @@ -557,6 +642,7 @@ while (gnt_util_onscreen_width(entry->scroll, entry->cursor) >= widget->priv.width) { entry->scroll = g_utf8_find_next_char(entry->scroll, NULL); } + update_kill_ring(entry, ENTRY_JAIL, NULL, 0); entry_redraw(widget); return TRUE; } @@ -570,6 +656,7 @@ int len = entry->end - iter + 1; if (len <= 0) return TRUE; + update_kill_ring(entry, ENTRY_DEL_FWD_WORD, entry->cursor, iter - entry->cursor); memmove(entry->cursor, iter, len); len = iter - entry->cursor; entry->end -= len; @@ -580,6 +667,42 @@ } static gboolean +transpose_chars(GntBindable *bind, GList *null) +{ + GntEntry *entry = GNT_ENTRY(bind); + char *current, *prev; + char hold[8]; /* that's right */ + + if (entry->cursor <= entry->start) + return FALSE; + + if (!*entry->cursor) + entry->cursor = g_utf8_find_prev_char(entry->start, entry->cursor); + + current = entry->cursor; + prev = g_utf8_find_prev_char(entry->start, entry->cursor); + move_forward(bind, null); + + /* Let's do this dance! */ + memcpy(hold, prev, current - prev); + memmove(prev, current, entry->cursor - current); + memcpy(prev + (entry->cursor - current), hold, current - prev); + + update_kill_ring(entry, ENTRY_JAIL, NULL, 0); + entry_redraw(GNT_WIDGET(entry)); + entry_text_changed(entry); + return TRUE; +} + +static gboolean +entry_yank(GntBindable *bind, GList *null) +{ + GntEntry *entry = GNT_ENTRY(bind); + gnt_entry_key_pressed(GNT_WIDGET(entry), entry->killring->buffer->str); + return TRUE; +} + +static gboolean gnt_entry_key_pressed(GntWidget *widget, const char *text) { GntEntry *entry = GNT_ENTRY(widget); @@ -594,80 +717,92 @@ return FALSE; } - else + + if ((text[0] == '\r' || text[0] == ' ') && entry->ddown) { - if ((text[0] == '\r' || text[0] == ' ') && entry->ddown) - { - char *text = g_strdup(gnt_tree_get_selection_data(GNT_TREE(entry->ddown))); - destroy_suggest(entry); - complete_suggest(entry, text); - g_free(text); - entry_text_changed(entry); - return TRUE; - } + char *text = g_strdup(gnt_tree_get_selection_data(GNT_TREE(entry->ddown))); + destroy_suggest(entry); + complete_suggest(entry, text); + g_free(text); + update_kill_ring(entry, ENTRY_JAIL, NULL, 0); + entry_text_changed(entry); + return TRUE; + } - if (!iscntrl(text[0])) - { - const char *str, *next; + if (!iscntrl(text[0])) + { + const char *str, *next; - for (str = text; *str; str = next) - { - int len; - next = g_utf8_find_next_char(str, NULL); - len = next - str; + for (str = text; *str; str = next) + { + int len; + next = g_utf8_find_next_char(str, NULL); + len = next - str; - /* Valid input? */ - /* XXX: Is it necessary to use _unichar_ variants here? */ - if (ispunct(*str) && (entry->flag & GNT_ENTRY_FLAG_NO_PUNCT)) - continue; - if (isspace(*str) && (entry->flag & GNT_ENTRY_FLAG_NO_SPACE)) - continue; - if (isalpha(*str) && !(entry->flag & GNT_ENTRY_FLAG_ALPHA)) - continue; - if (isdigit(*str) && !(entry->flag & GNT_ENTRY_FLAG_INT)) - continue; + /* Valid input? */ + /* XXX: Is it necessary to use _unichar_ variants here? */ + if (ispunct(*str) && (entry->flag & GNT_ENTRY_FLAG_NO_PUNCT)) + continue; + if (isspace(*str) && (entry->flag & GNT_ENTRY_FLAG_NO_SPACE)) + continue; + if (isalpha(*str) && !(entry->flag & GNT_ENTRY_FLAG_ALPHA)) + continue; + if (isdigit(*str) && !(entry->flag & GNT_ENTRY_FLAG_INT)) + continue; + + /* Reached the max? */ + if (entry->max && g_utf8_pointer_to_offset(entry->start, entry->end) >= entry->max) + continue; - /* Reached the max? */ - if (entry->max && g_utf8_pointer_to_offset(entry->start, entry->end) >= entry->max) - continue; + if (entry->end + len - entry->start >= entry->buffer) + { + /* This will cause the buffer to grow */ + char *tmp = g_strdup(entry->start); + gnt_entry_set_text_internal(entry, tmp); + g_free(tmp); + } - if (entry->end + len - entry->start >= entry->buffer) - { - /* This will cause the buffer to grow */ - char *tmp = g_strdup(entry->start); - gnt_entry_set_text_internal(entry, tmp); - g_free(tmp); - } - - memmove(entry->cursor + len, entry->cursor, entry->end - entry->cursor + 1); - entry->end += len; + memmove(entry->cursor + len, entry->cursor, entry->end - entry->cursor + 1); + entry->end += len; - while (str < next) - { - if (*str == '\r' || *str == '\n') - *entry->cursor = ' '; - else - *entry->cursor = *str; - entry->cursor++; - str++; - } + while (str < next) + { + if (*str == '\r' || *str == '\n') + *entry->cursor = ' '; + else + *entry->cursor = *str; + entry->cursor++; + str++; + } + + while (gnt_util_onscreen_width(entry->scroll, entry->cursor) >= widget->priv.width) + entry->scroll = g_utf8_find_next_char(entry->scroll, NULL); - while (gnt_util_onscreen_width(entry->scroll, entry->cursor) >= widget->priv.width) - entry->scroll = g_utf8_find_next_char(entry->scroll, NULL); + if (entry->ddown) + show_suggest_dropdown(entry); + } + update_kill_ring(entry, ENTRY_JAIL, NULL, 0); + entry_redraw(widget); + entry_text_changed(entry); + return TRUE; + } - if (entry->ddown) - show_suggest_dropdown(entry); - } - entry_redraw(widget); - entry_text_changed(entry); - return TRUE; - } + if (text[0] == '\r') { + gnt_widget_activate(widget); + return TRUE; } return FALSE; } static void +jail_killring(GntEntryKillRing *kr) +{ + g_string_free(kr->buffer, TRUE); + g_free(kr); +} + +static void gnt_entry_destroy(GntWidget *widget) { GntEntry *entry = GNT_ENTRY(widget); @@ -690,6 +825,8 @@ { gnt_widget_destroy(entry->ddown->parent); } + + jail_killring(entry->killring); } static void @@ -762,6 +899,10 @@ "\033" "f", NULL); gnt_bindable_class_register_action(bindable, "delete-next-word", delete_forward_word, "\033" "d", NULL); + gnt_bindable_class_register_action(bindable, "transpose-chars", transpose_chars, + GNT_KEY_CTRL_T, NULL); + gnt_bindable_class_register_action(bindable, "yank", entry_yank, + GNT_KEY_CTRL_Y, NULL); gnt_bindable_class_register_action(bindable, "suggest-show", suggest_show, "\t", NULL); gnt_bindable_class_register_action(bindable, "suggest-next", suggest_next, @@ -779,6 +920,14 @@ GNTDEBUG; } +static GntEntryKillRing * +new_killring() +{ + GntEntryKillRing *kr = g_new0(GntEntryKillRing, 1); + kr->buffer = g_string_new(NULL); + return kr; +} + static void gnt_entry_init(GTypeInstance *instance, gpointer class) { @@ -787,13 +936,14 @@ entry->flag = GNT_ENTRY_FLAG_ALL; entry->max = 0; - + entry->histlength = 0; entry->history = NULL; entry->word = TRUE; entry->always = FALSE; entry->suggests = NULL; + entry->killring = new_killring(); GNT_WIDGET_SET_FLAGS(GNT_WIDGET(entry), GNT_WIDGET_NO_BORDER | GNT_WIDGET_NO_SHADOW | GNT_WIDGET_CAN_TAKE_FOCUS); @@ -801,7 +951,7 @@ widget->priv.minw = 3; widget->priv.minh = 1; - + GNTDEBUG; } @@ -992,7 +1142,7 @@ if (!text || !*text) return; - + find = g_list_find_custom(entry->suggests, text, (GCompareFunc)g_utf8_collate); if (find) return;
--- a/finch/libgnt/gntentry.h Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gntentry.h Sat Nov 17 02:20:01 2007 +0000 @@ -48,6 +48,7 @@ typedef struct _GntEntry GntEntry; typedef struct _GntEntryPriv GntEntryPriv; typedef struct _GntEntryClass GntEntryClass; +typedef struct _GntEntryKillRing GntEntryKillRing; typedef enum { @@ -71,9 +72,9 @@ char *scroll; /* Current scrolling position */ char *cursor; /* Cursor location */ /* 0 <= cursor - scroll < widget-width */ - + size_t buffer; /* Size of the buffer */ - + int max; /* 0 means infinite */ gboolean masked; @@ -84,6 +85,7 @@ gboolean word; /* Are the suggestions for only a word, or for the whole thing? */ gboolean always; /* Should the list of suggestions show at all times, or only on tab-press? */ GntWidget *ddown; /* The dropdown with the suggested list */ + GntEntryKillRing *killring; /**< @since 2.3.0 */ }; struct _GntEntryClass
--- a/finch/libgnt/gntfilesel.c Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gntfilesel.c Sat Nov 17 02:20:01 2007 +0000 @@ -265,6 +265,7 @@ } g_free(fp); } + g_dir_close(dir); *files = g_list_reverse(*files); return TRUE; @@ -634,6 +635,7 @@ sel->cancel = gnt_button_new("Cancel"); sel->select = gnt_button_new("Select"); + g_signal_connect_swapped(G_OBJECT(sel->files), "activate", G_CALLBACK(gnt_widget_activate), sel->select); g_signal_connect(G_OBJECT(sel->select), "activate", G_CALLBACK(select_activated_cb), sel); } @@ -718,6 +720,7 @@ void gnt_file_sel_set_suggested_filename(GntFileSel *sel, const char *suggest) { + g_free(sel->suggest); sel->suggest = g_strdup(suggest); }
--- a/finch/libgnt/gntfilesel.h Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gntfilesel.h Sat Nov 17 02:20:01 2007 +0000 @@ -98,113 +98,123 @@ G_BEGIN_DECLS /** - * - * - * @return + * @return GType for GntFileSel. */ GType gnt_file_sel_get_gtype(void); /** - * + * Create a new file selector. * - * @return + * @return The newly created file selector. */ GntWidget * gnt_file_sel_new(void); /** - * - * @param sel - * @param path + * Set the current location of the file selector. * - * @return + * @param sel The file selector. + * @param path The current path of the selector. + * + * @return @c TRUE if the current location was successfully changed, @c FALSE otherwise. */ gboolean gnt_file_sel_set_current_location(GntFileSel *sel, const char *path); /** - * - * @param sel - * @param dirs + * Set wheter to only allow selecting directories. + * + * @param sel The file selector. + * @param dirs @c TRUE if only directories can be selected, @c FALSE if files + * can also be selected. */ void gnt_file_sel_set_dirs_only(GntFileSel *sel, gboolean dirs); /** - * - * @param sel + * Check whether the file selector allows only selecting directories. * - * @return + * @param sel The file selector. + * + * @return @c TRUE if only directories can be selected. */ gboolean gnt_file_sel_get_dirs_only(GntFileSel *sel); /** - * - * @param sel - * @param must + * Set whether a selected file must exist. + * + * @param sel The file selector. + * @param must @c TRUE if the selected file must exist. */ void gnt_file_sel_set_must_exist(GntFileSel *sel, gboolean must); /** - * - * @param sel + * Check whether the selector allows selecting non-existent files. * - * @return + * @param sel The file selector. + * + * @return @c TRUE if the selected file must exist, @c FALSE if a non-existent + * file can be selected. */ gboolean gnt_file_sel_get_must_exist(GntFileSel *sel); /** - * - * @param sel + * Get the selected file in the selector. * - * @return + * @param sel The file selector. + * + * @return The path of the selected file. The caller should g_free the returned + * string. */ char * gnt_file_sel_get_selected_file(GntFileSel *sel); - /* The returned value should be free'd */ - /** - * - * @param sel + * Get the list of selected files in the selector. * - * @return + * @param sel The file selector. + * + * @return A list of paths for the selected files. The caller must g_free the + * contents of the list, and g_list_free the list. */ GList * gnt_file_sel_get_selected_multi_files(GntFileSel *sel); /** - * - * @param sel - * @param set + * Allow selecting multiple files. + * + * @param sel The file selector. + * @param set @c TRUE if selecting multiple files should be allowed. */ void gnt_file_sel_set_multi_select(GntFileSel *sel, gboolean set); /** - * - * @param sel - * @param suggest + * Set the suggested file to have selected at startup. + * + * @param sel The file selector. + * @param suggest The suggested filename. */ void gnt_file_sel_set_suggested_filename(GntFileSel *sel, const char *suggest); /** - * - * @param sel - * @param path - * @param files - * @param error) + * Set custom functions to read the names of files. + * + * @param sel The file selector. + * @param read_fn The custom read function. */ void gnt_file_sel_set_read_fn(GntFileSel *sel, gboolean (*read_fn)(const char *path, GList **files, GError **error)); /** - * - * @param name - * @param size + * Create a new GntFile. * - * @return + * @param name The name of the file. + * @param size The size of the file. + * + * @return The newly created GntFile. */ GntFile* gnt_file_new(const char *name, unsigned long size); /** - * - * @param name + * Create a new GntFile for a directory. * - * @return + * @param name The name of the directory. + * + * @return The newly created GntFile. */ GntFile* gnt_file_new_dir(const char *name);
--- a/finch/libgnt/gntkeys.h Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gntkeys.h Sat Nov 17 02:20:01 2007 +0000 @@ -104,41 +104,59 @@ #define GNT_KEY_F12 SAFE(key_f12) /** - * This will do stuff with the terminal settings and stuff. - */ -/** - * + * Initialize the keys. */ void gnt_init_keys(void); /** - * - * @param text + * Refine input text. This usually looks at what the terminal claims it is, + * and tries to change the text to work around some oft-broken terminfo entries. + * + * @param text The input text to refine. */ void gnt_keys_refine(char *text); +/** + * Translate a user-readable representation of an input to a machine-readable representation. + * + * @param name The user-readable representation of an input (eg.: c-t) + * + * @return A machine-readable representation of the input. + */ const char *gnt_key_translate(const char *name); + +/** + * Translate a machine-readable representation of an input to a user-readable representation. + * + * @param key The machine-readable representation of an input. + * + * @return A user-readable representation of the input (eg.: c-t). + */ const char *gnt_key_lookup(const char *key); /** - * - * @param path + * Add a key combination to the internal key-tree. + * + * @param key The key to add */ -void gnt_keys_add_combination(const char *path); +void gnt_keys_add_combination(const char *key); /** - * - * @param path + * Remove a key combination from the internal key-tree. + * + * @param key The key to remove. */ -void gnt_keys_del_combination(const char *path); +void gnt_keys_del_combination(const char *key); /** - * - * @param path + * Find a combination from the given string. + * + * @param key The input string. * - * @return + * @return The number of bytes in the combination that starts at the beginning + * of key (can be 0). */ -int gnt_keys_find_combination(const char *path); +int gnt_keys_find_combination(const char *key); /* A lot of commonly used variable names are defined in <term.h>. * #undef them to make life easier for everyone. */
--- a/finch/libgnt/gntlabel.c Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gntlabel.c Sat Nov 17 02:20:01 2007 +0000 @@ -27,6 +27,13 @@ enum { + PROP_0, + PROP_TEXT, + PROP_TEXT_FLAG +}; + +enum +{ SIGS = 1, }; @@ -61,14 +68,75 @@ } static void +gnt_label_set_property(GObject *obj, guint prop_id, const GValue *value, + GParamSpec *spec) +{ + GntLabel *label = GNT_LABEL(obj); + switch (prop_id) { + case PROP_TEXT: + g_free(label->text); + label->text = gnt_util_onscreen_fit_string(g_value_get_string(value), -1); + break; + case PROP_TEXT_FLAG: + label->flags = g_value_get_int(value); + break; + default: + g_return_if_reached(); + break; + } +} + +static void +gnt_label_get_property(GObject *obj, guint prop_id, GValue *value, + GParamSpec *spec) +{ + GntLabel *label = GNT_LABEL(obj); + switch (prop_id) { + case PROP_TEXT: + g_value_set_string(value, label->text); + break; + case PROP_TEXT_FLAG: + g_value_set_int(value, label->flags); + break; + default: + break; + } +} + +static void gnt_label_class_init(GntLabelClass *klass) { + GObjectClass *gclass = G_OBJECT_CLASS(klass); + parent_class = GNT_WIDGET_CLASS(klass); parent_class->destroy = gnt_label_destroy; parent_class->draw = gnt_label_draw; parent_class->map = NULL; parent_class->size_request = gnt_label_size_request; + gclass->set_property = gnt_label_set_property; + gclass->get_property = gnt_label_get_property; + + g_object_class_install_property(gclass, + PROP_TEXT, + g_param_spec_string("text", "Text", + "The text for the label.", + NULL, + G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB + ) + ); + + g_object_class_install_property(gclass, + PROP_TEXT_FLAG, + g_param_spec_int("text-flag", "Text flag", + "Text attribute to use when displaying the text in the label.", + GNT_TEXT_FLAG_NORMAL, + GNT_TEXT_FLAG_NORMAL|GNT_TEXT_FLAG_BOLD|GNT_TEXT_FLAG_UNDERLINE| + GNT_TEXT_FLAG_BLINK|GNT_TEXT_FLAG_DIM|GNT_TEXT_FLAG_HIGHLIGHT, + GNT_TEXT_FLAG_NORMAL, + G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB + ) + ); GNTDEBUG; } @@ -76,6 +144,8 @@ gnt_label_init(GTypeInstance *instance, gpointer class) { GntWidget *widget = GNT_WIDGET(instance); + gnt_widget_set_take_focus(widget, FALSE); + GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_NO_BORDER | GNT_WIDGET_NO_SHADOW); GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_GROW_X); widget->priv.minw = 3; widget->priv.minh = 1; @@ -120,21 +190,13 @@ GntWidget *gnt_label_new_with_format(const char *text, GntTextFormatFlags flags) { - GntWidget *widget = g_object_new(GNT_TYPE_LABEL, NULL); - GntLabel *label = GNT_LABEL(widget); - - label->text = gnt_util_onscreen_fit_string(text, -1); - label->flags = flags; - gnt_widget_set_take_focus(widget, FALSE); - GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_NO_BORDER | GNT_WIDGET_NO_SHADOW); - + GntWidget *widget = g_object_new(GNT_TYPE_LABEL, "text-flag", flags, "text", text, NULL); return widget; } void gnt_label_set_text(GntLabel *label, const char *text) { - g_free(label->text); - label->text = gnt_util_onscreen_fit_string(text, -1); + g_object_set(label, "text", text, NULL); if (GNT_WIDGET(label)->window) {
--- a/finch/libgnt/gntlabel.h Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gntlabel.h Sat Nov 17 02:20:01 2007 +0000 @@ -67,33 +67,34 @@ G_BEGIN_DECLS /** - * - * - * @return + * @return GType for GntLabel. */ GType gnt_label_get_gtype(void); /** - * - * @param text + * Create a new GntLabel. * - * @return + * @param text The text of the label. + * + * @return The newly created label. */ GntWidget * gnt_label_new(const char *text); /** - * - * @param text - * @param flags + * Create a new label with specified text attributes. * - * @return + * @param text The text. + * @param flags Text attributes for the text. + * + * @return The newly created label. */ GntWidget * gnt_label_new_with_format(const char *text, GntTextFormatFlags flags); /** - * - * @param label - * @param text + * Change the text of a label. + * + * @param label The label. + * @param text The new text to set in the label. */ void gnt_label_set_text(GntLabel *label, const char *text);
--- a/finch/libgnt/gntline.c Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gntline.c Sat Nov 17 02:20:01 2007 +0000 @@ -40,10 +40,10 @@ { GntLine *line = GNT_LINE(widget); if (line->vertical) - mvwvline(widget->window, 1, 0, ACS_VLINE | COLOR_PAIR(GNT_COLOR_NORMAL), + mvwvline(widget->window, 1, 0, ACS_VLINE | gnt_color_pair(GNT_COLOR_NORMAL), widget->priv.height - 2); else - mvwhline(widget->window, 0, 1, ACS_HLINE | COLOR_PAIR(GNT_COLOR_NORMAL), + mvwhline(widget->window, 0, 1, ACS_HLINE | gnt_color_pair(GNT_COLOR_NORMAL), widget->priv.width - 2); }
--- a/finch/libgnt/gntline.h Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gntline.h Sat Nov 17 02:20:01 2007 +0000 @@ -67,9 +67,7 @@ G_BEGIN_DECLS /** - * - * - * @return + * @return GType for GntLine. */ GType gnt_line_get_gtype(void); @@ -77,10 +75,11 @@ #define gnt_vline_new() gnt_line_new(TRUE) /** - * - * @param vertical + * Create new line * - * @return + * @param vertical @c TRUE if the line should be vertical, @c FALSE for a horizontal line. + * + * @return The newly created line. */ GntWidget * gnt_line_new(gboolean vertical);
--- a/finch/libgnt/gntmain.c Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gntmain.c Sat Nov 17 02:20:01 2007 +0000 @@ -291,7 +291,8 @@ k += p; } end: - gnt_wm_set_event_stack(wm, FALSE); + if (wm) + gnt_wm_set_event_stack(wm, FALSE); g_free(cvrt); return TRUE; } @@ -482,7 +483,7 @@ gnt_init_colors(); - wbkgdset(stdscr, '\0' | COLOR_PAIR(GNT_COLOR_NORMAL)); + wbkgdset(stdscr, '\0' | gnt_color_pair(GNT_COLOR_NORMAL)); refresh(); #ifdef ALL_MOUSE_EVENTS @@ -490,7 +491,7 @@ mousemask(ALL_MOUSE_EVENTS | REPORT_MOUSE_POSITION, NULL); #endif - wbkgdset(stdscr, '\0' | COLOR_PAIR(GNT_COLOR_NORMAL)); + wbkgdset(stdscr, '\0' | gnt_color_pair(GNT_COLOR_NORMAL)); werase(stdscr); wrefresh(stdscr); @@ -646,7 +647,7 @@ return TRUE; } -void gnt_set_clipboard_string(gchar *string) +void gnt_set_clipboard_string(const gchar *string) { gnt_clipboard_set_string(clipboard, string); }
--- a/finch/libgnt/gntmenu.c Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gntmenu.c Sat Nov 17 02:20:01 2007 +0000 @@ -48,6 +48,14 @@ static gboolean (*org_key_pressed)(GntWidget *w, const char *t); static void +menu_hide_all(GntMenu *menu) +{ + while (menu->parentmenu) + menu = menu->parentmenu; + gnt_widget_hide(GNT_WIDGET(menu)); +} + +static void gnt_menu_draw(GntWidget *widget) { GntMenu *menu = GNT_MENU(widget); @@ -56,12 +64,12 @@ int i; if (menu->type == GNT_MENU_TOPLEVEL) { - wbkgdset(widget->window, '\0' | COLOR_PAIR(GNT_COLOR_HIGHLIGHT)); + wbkgdset(widget->window, '\0' | gnt_color_pair(GNT_COLOR_HIGHLIGHT)); werase(widget->window); for (i = 0, iter = menu->list; iter; iter = iter->next, i++) { GntMenuItem *item = GNT_MENU_ITEM(iter->data); - type = ' ' | COLOR_PAIR(GNT_COLOR_HIGHLIGHT); + type = ' ' | gnt_color_pair(GNT_COLOR_HIGHLIGHT); if (i == menu->selected) type |= A_REVERSE; item->priv.x = getcurx(widget->window) + widget->priv.x; @@ -181,7 +189,12 @@ static void menuitem_activate(GntMenu *menu, GntMenuItem *item) { - if (item) { + if (!item) + return; + + if (gnt_menuitem_activate(item)) { + menu_hide_all(menu); + } else { if (item->submenu) { GntMenu *sub = GNT_MENU(item->submenu); menu->submenu = sub; @@ -195,12 +208,8 @@ gnt_widget_set_position(GNT_WIDGET(sub), item->priv.x, item->priv.y); GNT_WIDGET_UNSET_FLAGS(GNT_WIDGET(sub), GNT_WIDGET_INVISIBLE); gnt_widget_draw(GNT_WIDGET(sub)); - } else if (item->callback) { - item->callback(item, item->callbackdata); - while (menu) { - gnt_widget_hide(GNT_WIDGET(menu)); - menu = menu->parentmenu; - } + } else { + menu_hide_all(menu); } } } @@ -226,7 +235,7 @@ 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); @@ -286,10 +295,8 @@ if (current != menu->selected) { GntMenu *sub = menu->submenu; - while (sub) { + if (sub) gnt_widget_hide(GNT_WIDGET(sub)); - sub = sub->submenu; - } gnt_widget_draw(widget); return TRUE; } @@ -326,8 +333,7 @@ GntMenu *menu = GNT_MENU(tree); gboolean check = gnt_menuitem_check_get_checked(GNT_MENU_ITEM_CHECK(item)); gnt_menuitem_check_set_checked(GNT_MENU_ITEM_CHECK(item), !check); - if (item->callback) - item->callback(item, item->callbackdata); + gnt_menuitem_activate(item); while (menu) { gnt_widget_hide(GNT_WIDGET(menu)); menu = menu->parentmenu; @@ -484,9 +490,6 @@ item = NULL; } - if (item) - menuitem_activate(menu, item); - return item; }
--- a/finch/libgnt/gntmenu.h Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gntmenu.h Sat Nov 17 02:20:01 2007 +0000 @@ -108,12 +108,14 @@ void gnt_menu_add_item(GntMenu *menu, GntMenuItem *item); /** - * Get the GntMenuItem with the given ID. + * Return the GntMenuItem with the given ID. * * @param menu The menu. * @param id The ID for an item. * * @return The menuitem with the given ID, or @c NULL. + * + * @since 2.3.0 */ GntMenuItem *gnt_menu_get_item(GntMenu *menu, const char *id);
--- a/finch/libgnt/gntmenuitem.c Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gntmenuitem.c Sat Nov 17 02:20:01 2007 +0000 @@ -23,6 +23,13 @@ #include "gntmenu.h" #include "gntmenuitem.h" +enum +{ + SIG_ACTIVATE, + SIGS +}; +static guint signals[SIGS] = { 0 }; + static GObjectClass *parent_class = NULL; static void @@ -44,10 +51,18 @@ parent_class = g_type_class_peek_parent(klass); obj_class->dispose = gnt_menuitem_destroy; + + signals[SIG_ACTIVATE] = + g_signal_new("activate", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); } static void -gnt_menuitem_init(GTypeInstance *instance, gpointer class) +gnt_menuitem_init(GTypeInstance *instance, gpointer klass) { } @@ -131,3 +146,13 @@ return item->priv.id; } +gboolean gnt_menuitem_activate(GntMenuItem *item) +{ + g_signal_emit(item, signals[SIG_ACTIVATE], 0); + if (item->callback) { + item->callback(item, item->callbackdata); + return TRUE; + } + return FALSE; +} +
--- a/finch/libgnt/gntmenuitem.h Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gntmenuitem.h Sat Nov 17 02:20:01 2007 +0000 @@ -123,6 +123,8 @@ * @param item The menuitem. * * @return The submenu, or @c NULL. + * + * @since 2.3.0 */ GntMenu *gnt_menuitem_get_submenu(GntMenuItem *item); @@ -150,6 +152,8 @@ * * @param item The menuitem. * @param id The ID for the menuitem. + * + * @since 2.3.0 */ void gnt_menuitem_set_id(GntMenuItem *item, const char *id); @@ -159,9 +163,24 @@ * @param item The menuitem. * * @return The ID for the menuitem. + * + * @since 2.3.0 */ const char * gnt_menuitem_get_id(GntMenuItem *item); +/** + * Activate a menuitem. + * Activating the menuitem will first trigger the 'activate' signal for the + * menuitem. Then the callback for the menuitem is triggered, if there is one. + * + * @param item The menuitem. + * + * @return Whether the callback for the menuitem was called. + * + * @since 2.3.0 + */ +gboolean gnt_menuitem_activate(GntMenuItem *item); + G_END_DECLS #endif /* GNT_MENUITEM_H */
--- a/finch/libgnt/gntmenuitemcheck.h Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gntmenuitemcheck.h Sat Nov 17 02:20:01 2007 +0000 @@ -66,32 +66,33 @@ G_BEGIN_DECLS /** - * - * - * @return + * @return GType for GntMenuItemCheck. */ GType gnt_menuitem_check_get_gtype(void); /** - * - * @param text + * Create a new menuitem. * - * @return + * @param text The text for the menuitem. + * + * @return The newly created menuitem. */ GntMenuItem * gnt_menuitem_check_new(const char *text); /** - * - * @param item + * Check whether the menuitem is checked or not. * - * @return + * @param item The menuitem. + * + * @return @c TRUE if the item is checked, @c FALSE otherwise. */ gboolean gnt_menuitem_check_get_checked(GntMenuItemCheck *item); /** - * - * @param item - * @param set + * Set whether the menuitem is checked or not. + * + * @param item The menuitem. + * @param set @c TRUE if the item should be checked, @c FALSE otherwise. */ void gnt_menuitem_check_set_checked(GntMenuItemCheck *item, gboolean set);
--- a/finch/libgnt/gntslider.c Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gntslider.c Sat Nov 17 02:20:01 2007 +0000 @@ -84,21 +84,21 @@ else position = 0; if (slider->vertical) { - mvwvline(widget->window, size-position, 0, ACS_VLINE | COLOR_PAIR(GNT_COLOR_NORMAL) | A_BOLD, + mvwvline(widget->window, size-position, 0, ACS_VLINE | gnt_color_pair(GNT_COLOR_NORMAL) | A_BOLD, position); - mvwvline(widget->window, 0, 0, ACS_VLINE | COLOR_PAIR(GNT_COLOR_NORMAL), + mvwvline(widget->window, 0, 0, ACS_VLINE | gnt_color_pair(GNT_COLOR_NORMAL), size-position); } else { - mvwhline(widget->window, 0, 0, ACS_HLINE | COLOR_PAIR(GNT_COLOR_NORMAL) | A_BOLD, + mvwhline(widget->window, 0, 0, ACS_HLINE | gnt_color_pair(GNT_COLOR_NORMAL) | A_BOLD, position); - mvwhline(widget->window, 0, position, ACS_HLINE | COLOR_PAIR(GNT_COLOR_NORMAL), + mvwhline(widget->window, 0, position, ACS_HLINE | gnt_color_pair(GNT_COLOR_NORMAL), size - position); } mvwaddch(widget->window, slider->vertical ? (size - position - 1) : 0, slider->vertical ? 0 : position, - ACS_CKBOARD | COLOR_PAIR(attr)); + ACS_CKBOARD | gnt_color_pair(attr)); } static void
--- a/finch/libgnt/gntslider.h Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gntslider.h Sat Nov 17 02:20:01 2007 +0000 @@ -75,6 +75,8 @@ /** * @return The GType for GntSlider + * + * @since 2.0.0 (gnt), 2.1.0 (pidgin) */ GType gnt_slider_get_gtype(void); @@ -89,6 +91,8 @@ * @param min The minimum value for the slider * * @return The newly created slider + * + * @since 2.0.0 (gnt), 2.1.0 (pidgin) */ GntWidget * gnt_slider_new(gboolean orient, int max, int min); @@ -98,6 +102,8 @@ * @param slider The slider * @param max The maximum value * @param min The minimum value + * + * @since 2.0.0 (gnt), 2.1.0 (pidgin) */ void gnt_slider_set_range(GntSlider *slider, int max, int min); @@ -106,6 +112,8 @@ * * @param slider The slider * @param step The amount for each step + * + * @since 2.0.0 (gnt), 2.1.0 (pidgin) */ void gnt_slider_set_step(GntSlider *slider, int step); @@ -114,6 +122,8 @@ * * @param slider The slider * @param step The amount for a small step (for the slider) + * + * @since 2.2.0 */ void gnt_slider_set_small_step(GntSlider *slider, int step); @@ -122,6 +132,8 @@ * * @param slider The slider * @param step The amount for a large step (for the slider) + * + * @since 2.2.0 */ void gnt_slider_set_large_step(GntSlider *slider, int step); @@ -133,6 +145,8 @@ * forward, negative to change backward * * @return The value of the slider after the change + * + * @since 2.0.0 (gnt), 2.1.0 (pidgin) */ int gnt_slider_advance_step(GntSlider *slider, int steps); @@ -141,6 +155,8 @@ * * @param slider The slider * @param value The current value + * + * @since 2.0.0 (gnt), 2.1.0 (pidgin) */ void gnt_slider_set_value(GntSlider *slider, int value); @@ -149,6 +165,8 @@ * * @param slider The slider * + * + * @since 2.0.0 (gnt), 2.1.0 (pidgin) */ int gnt_slider_get_value(GntSlider *slider); @@ -157,6 +175,8 @@ * * @param slider The slider * @param label The label to update + * + * @since 2.0.0 (gnt), 2.1.0 (pidgin) */ void gnt_slider_reflect_label(GntSlider *slider, GntLabel *label);
--- a/finch/libgnt/gntstyle.c Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gntstyle.c Sat Nov 17 02:20:01 2007 +0000 @@ -413,11 +413,14 @@ void gnt_uninit_styles() { int i; - for (i = 0; i < GNT_STYLES; i++) + for (i = 0; i < GNT_STYLES; i++) { g_free(str_styles[i]); + str_styles[i] = NULL; + } #if GLIB_CHECK_VERSION(2,6,0) g_key_file_free(gkfile); + gkfile = NULL; #endif }
--- a/finch/libgnt/gntstyle.h Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gntstyle.h Sat Nov 17 02:20:01 2007 +0000 @@ -38,11 +38,17 @@ } GntStyle; /** - * - * @param filename + * Read configuration from a file. + * + * @param filename The filename to read configuration from. */ void gnt_style_read_configure_file(const char *filename); +/** + * Get the user-setting for a style. + * @param style The style. + * @return The user-setting, or @c NULL. + */ const char *gnt_style_get(GntStyle style); /** @@ -53,6 +59,8 @@ * @param key The key * * @return The value of the setting as a string, or @c NULL + * + * @since 2.0.0 (gnt), 2.1.0 (pidgin) */ char *gnt_style_get_from_name(const char *group, const char *key); @@ -62,34 +70,33 @@ * * @param value The value of the boolean setting as a string * @return The boolean value + * + * @since 2.0.0 (gnt), 2.1.0 (pidgin) */ gboolean gnt_style_parse_bool(const char *value); /** - * - * @param style - * @param def + * Get the boolean value for a user-setting. * - * @return + * @param style The style. + * @param def The default value (i.e, the value if the user didn't define + * any value) + * + * @return The value of the setting. */ gboolean gnt_style_get_bool(GntStyle style, gboolean def); -/* This should be called only once for the each type */ /** - * - * @param type - * @param hash + * @internal */ void gnt_styles_get_keyremaps(GType type, GHashTable *hash); /** - * - * @param type - * @param klass + * @internal */ void gnt_style_read_actions(GType type, GntBindableClass *klass); -/* +/** * Read menu-accels from ~/.gntrc * * @param name The name of the window. @@ -99,15 +106,20 @@ */ gboolean gnt_style_read_menu_accels(const char *name, GHashTable *table); +/** + * @internal + * Read workspace information. + */ void gnt_style_read_workspaces(GntWM *wm); /** - * + * Initialize style settings. */ void gnt_init_styles(void); /** - * + * Uninitialize style settings. */ void gnt_uninit_styles(void); +
--- a/finch/libgnt/gnttextview.c Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gnttextview.c Sat Nov 17 02:20:01 2007 +0000 @@ -61,6 +61,8 @@ static gchar *select_end; static gboolean double_click; +static void reset_text_view(GntTextView *view); + static void gnt_text_view_draw(GntWidget *widget) { @@ -71,7 +73,7 @@ int comp = 0; /* Used for top-aligned text */ gboolean has_scroll = !(view->flags & GNT_TEXT_VIEW_NO_SCROLL); - wbkgd(widget->window, COLOR_PAIR(GNT_COLOR_NORMAL)); + wbkgd(widget->window, gnt_color_pair(GNT_COLOR_NORMAL)); werase(widget->window); if ((view->flags & GNT_TEXT_VIEW_TOP_ALIGN) && @@ -158,15 +160,15 @@ position = rows - showing; mvwvline(widget->window, position + 1, scrcol, - ACS_CKBOARD | COLOR_PAIR(GNT_COLOR_HIGHLIGHT_D), showing); + ACS_CKBOARD | gnt_color_pair(GNT_COLOR_HIGHLIGHT_D), showing); } if (has_scroll) { mvwaddch(widget->window, 0, scrcol, - (lines ? ACS_UARROW : ' ') | COLOR_PAIR(GNT_COLOR_HIGHLIGHT_D)); + (lines ? ACS_UARROW : ' ') | gnt_color_pair(GNT_COLOR_HIGHLIGHT_D)); mvwaddch(widget->window, widget->priv.height - 1, scrcol, ((view->list && view->list->prev) ? ACS_DARROW : ' ') | - COLOR_PAIR(GNT_COLOR_HIGHLIGHT_D)); + gnt_color_pair(GNT_COLOR_HIGHLIGHT_D)); } GNTDEBUG; @@ -370,7 +372,7 @@ string = view->string; view->string = NULL; - gnt_text_view_clear(view); + reset_text_view(view); view->string = g_string_set_size(view->string, string->len); view->string->len = 0; @@ -645,16 +647,16 @@ fl |= A_BLINK; if (flags & GNT_TEXT_FLAG_DIM) - fl |= (A_DIM | COLOR_PAIR(GNT_COLOR_DISABLED)); + fl |= (A_DIM | gnt_color_pair(GNT_COLOR_DISABLED)); else if (flags & GNT_TEXT_FLAG_HIGHLIGHT) - fl |= (A_DIM | COLOR_PAIR(GNT_COLOR_HIGHLIGHT)); + fl |= (A_DIM | gnt_color_pair(GNT_COLOR_HIGHLIGHT)); else - fl |= COLOR_PAIR(GNT_COLOR_NORMAL); + fl |= gnt_color_pair(GNT_COLOR_NORMAL); return fl; } -void gnt_text_view_clear(GntTextView *view) +static void reset_text_view(GntTextView *view) { GntTextLine *line; @@ -667,6 +669,14 @@ if (view->string) g_string_free(view->string, TRUE); view->string = g_string_new(NULL); +} + +void gnt_text_view_clear(GntTextView *view) +{ + reset_text_view(view); + + g_list_foreach(view->tags, free_tag, NULL); + view->tags = NULL; if (GNT_WIDGET(view)->window) gnt_widget_draw(GNT_WIDGET(view)); @@ -833,7 +843,7 @@ if (status == 0) { char *text = NULL; if (g_file_get_contents(pageditor.file, &text, NULL, NULL)) { - gnt_text_view_clear(pageditor.tv); + reset_text_view(pageditor.tv); gnt_text_view_append_text_with_flags(pageditor.tv, text, GNT_TEXT_FLAG_NORMAL); gnt_text_view_scroll(GNT_TEXT_VIEW(pageditor.tv), 0); g_free(text);
--- a/finch/libgnt/gnttextview.h Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gnttextview.h Sat Nov 17 02:20:01 2007 +0000 @@ -88,122 +88,152 @@ G_BEGIN_DECLS /** - * - * - * @return + * @return GType for GntTextView. */ GType gnt_text_view_get_gtype(void); -/* XXX: For now, don't set a textview to have any border. - * If you want borders real bad, put it in a box. */ /** - * + * Create a new textview. * - * @return + * @return The newly created textview. */ GntWidget * gnt_text_view_new(void); -/* scroll > 0 means scroll up, < 0 means scroll down, == 0 means scroll to the end */ /** - * - * @param view - * @param scroll + * Scroll the textview. + * @param view The textview to scroll. + * @param scroll scroll > 0 means scroll up, < 0 means scroll down, == 0 means scroll to the end. */ void gnt_text_view_scroll(GntTextView *view, int scroll); /** - * - * @param view - * @param text - * @param flags + * Append new text in a textview. + * + * @param view The textview. + * @param text The text to append to the textview. + * @param flags The text-flags to apply to the new text. */ void gnt_text_view_append_text_with_flags(GntTextView *view, const char *text, GntTextFormatFlags flags); /** - * - * @param view - * @param text - * @param flags - * @param tag + * Append text in the textview, with some identifier (tag) for the added text. + * + * @param view The textview. + * @param text The text to append. + * @param flags The text-flags to apply to the new text. + * @param tag The tag for the appended text, so it can be changed later (@see gnt_text_view_tag_change) */ void gnt_text_view_append_text_with_tag(GntTextView *view, const char *text, GntTextFormatFlags flags, const char *tag); -/* Move the cursor to the beginning of the next line and resets text-attributes. - * It first completes the current line with the current text-attributes. */ /** - * - * @param view + * Move the cursor to the beginning of the next line and resets text-attributes. + * It first completes the current line with the current text-attributes. + * + * @param view The textview. */ void gnt_text_view_next_line(GntTextView *view); /** - * - * @param flags + * Convert GNT-text formats to ncurses-text attributes. * - * @return + * @param flags The GNT text format. + * + * @return Nucrses text attribute. */ chtype gnt_text_format_flag_to_chtype(GntTextFormatFlags flags); /** - * - * @param view + * Clear the contents of the textview. + * + * @param view The textview. */ void gnt_text_view_clear(GntTextView *view); /** - * - * @param view + * The number of lines below the bottom-most visible line. * - * @return + * @param view The textview. + * + * @return Number of lines below the bottom-most visible line. */ int gnt_text_view_get_lines_below(GntTextView *view); /** - * - * @param view + * The number of lines above the topmost visible line. * - * @return + * @param view The textview. + * + * @return Number of lines above the topmost visible line. */ int gnt_text_view_get_lines_above(GntTextView *view); -/* If text is NULL, then the tag is removed. */ /** - * - * @param view - * @param name - * @param text - * @param all + * Change the text of a tag. * - * @return + * @param view The textview. + * @param name The name of the tag. + * @param text The new text for the text. If 'text' is @c NULL, the tag is removed. + * @param all @c TRUE if all of the instancess of the tag should be changed, @c FALSE if + * only the first instance should be changed. + * + * @return The number of instances changed. */ int gnt_text_view_tag_change(GntTextView *view, const char *name, const char *text, gboolean all); /** - * - * @param view - * @param widget + * Setup hooks so that pressing up/down/page-up/page-down keys when 'widget' is + * in focus scrolls the textview. + * + * @param view The textview. + * @param widget The trigger widget. */ void gnt_text_view_attach_scroll_widget(GntTextView *view, GntWidget *widget); /** - * - * @param view - * @param widget + * Setup appropriate hooks so that pressing some keys when the 'pager' widget + * is in focus triggers the PAGER to popup with the contents of the textview + * in it. + * + * The default key-combination to trigger the pager is a-v, and the default + * PAGER application is $PAGER. Both can be changed in ~/.gntrc like this: + * + * @code + * [pager] + * key = a-v + * path = /path/to/pager + * @endcode + * + * @param view The textview. + * @param pager The widget to trigger the PAGER. */ void gnt_text_view_attach_pager_widget(GntTextView *view, GntWidget *pager); /** - * - * @param view - * @param widget + * Setup appropriate hooks so that pressing some keys when 'widget' + * is in focus triggers the EDITOR to popup with the contents of the textview + * in it. + * + * The default key-combination to trigger the pager is a-e, and the default + * EDITOR application is $EDITOR. Both can be changed in ~/.gntrc like this: + * + * @code + * [editor] + * key = a-e + * path = /path/to/editor + * @endcode + * + * @param view The textview. + * @param widget The widget to trigger the EDITOR. */ -void gnt_text_view_attach_editor_widget(GntTextView *view, GntWidget *pager); +void gnt_text_view_attach_editor_widget(GntTextView *view, GntWidget *widget); /** * Set a GntTextViewFlag for the textview widget. * * @param view The textview widget * @param flag The flag to set + * + * @since 2.0.0 (gnt), 2.1.0 (pidgin) */ void gnt_text_view_set_flag(GntTextView *view, GntTextViewFlag flag);
--- a/finch/libgnt/gnttree.c Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gnttree.c Sat Nov 17 02:20:01 2007 +0000 @@ -363,17 +363,17 @@ notfirst = TRUE; if (len > width) { - len = width - 1; + len = MAX(1, width - 1); cut = TRUE; } if (RIGHT_ALIGNED(tree, i) && len < tree->columns[i].width) { - g_string_append_printf(string, "%*s", width - len, ""); + g_string_append_printf(string, "%*s", width - len - cut, ""); } text = gnt_util_onscreen_width_to_pointer(display, len - fl, NULL); string = g_string_append_len(string, display, text - display); - if (cut) { /* ellipsis */ + if (cut && width > 1) { /* ellipsis */ if (gnt_ascii_only()) g_string_append_c(string, '~'); else @@ -432,7 +432,7 @@ tree_selection_changed(tree, NULL, tree->current); } - wbkgd(widget->window, COLOR_PAIR(GNT_COLOR_NORMAL)); + wbkgd(widget->window, gnt_color_pair(GNT_COLOR_NORMAL)); start = 0; if (tree->show_title) @@ -440,9 +440,9 @@ int i; int x = pos; - mvwhline(widget->window, pos + 1, pos, ACS_HLINE | COLOR_PAIR(GNT_COLOR_NORMAL), + mvwhline(widget->window, pos + 1, pos, ACS_HLINE | gnt_color_pair(GNT_COLOR_NORMAL), widget->priv.width - pos - 1); - mvwhline(widget->window, pos, pos, ' ' | COLOR_PAIR(GNT_COLOR_NORMAL), + mvwhline(widget->window, pos, pos, ' ' | gnt_color_pair(GNT_COLOR_NORMAL), widget->priv.width - pos - 1); for (i = 0; i < tree->ncol; i++) @@ -455,14 +455,15 @@ } if (pos) { - tree_mark_columns(tree, pos, 0, ACS_TTEE | COLOR_PAIR(GNT_COLOR_NORMAL)); + tree_mark_columns(tree, pos, 0, + (tree->show_separator ? ACS_TTEE : ACS_HLINE) | gnt_color_pair(GNT_COLOR_NORMAL)); tree_mark_columns(tree, pos, widget->priv.height - pos, - ACS_BTEE | COLOR_PAIR(GNT_COLOR_NORMAL)); + (tree->show_separator ? ACS_BTEE : ACS_HLINE) | gnt_color_pair(GNT_COLOR_NORMAL)); } tree_mark_columns(tree, pos, pos + 1, - (tree->show_separator ? ACS_PLUS : ACS_HLINE) | COLOR_PAIR(GNT_COLOR_NORMAL)); + (tree->show_separator ? ACS_PLUS : ACS_HLINE) | gnt_color_pair(GNT_COLOR_NORMAL)); tree_mark_columns(tree, pos, pos, - (tree->show_separator ? ACS_VLINE : ' ') | COLOR_PAIR(GNT_COLOR_NORMAL)); + (tree->show_separator ? ACS_VLINE : ' ') | gnt_color_pair(GNT_COLOR_NORMAL)); start = 2; } @@ -514,18 +515,18 @@ if (row == tree->current) { if (gnt_widget_has_focus(widget)) - attr |= COLOR_PAIR(GNT_COLOR_HIGHLIGHT); + attr |= gnt_color_pair(GNT_COLOR_HIGHLIGHT); else - attr |= COLOR_PAIR(GNT_COLOR_HIGHLIGHT_D); + attr |= gnt_color_pair(GNT_COLOR_HIGHLIGHT_D); } else { if (flags & GNT_TEXT_FLAG_DIM) - attr |= (A_DIM | COLOR_PAIR(GNT_COLOR_DISABLED)); + attr |= (A_DIM | gnt_color_pair(GNT_COLOR_DISABLED)); else if (flags & GNT_TEXT_FLAG_HIGHLIGHT) - attr |= (A_DIM | COLOR_PAIR(GNT_COLOR_HIGHLIGHT)); + attr |= (A_DIM | gnt_color_pair(GNT_COLOR_HIGHLIGHT)); else - attr |= COLOR_PAIR(GNT_COLOR_NORMAL); + attr |= gnt_color_pair(GNT_COLOR_NORMAL); } wbkgdset(widget->window, '\0' | attr); @@ -537,7 +538,7 @@ (tree->show_separator ? ACS_VLINE : ' ') | attr); } - wbkgdset(widget->window, '\0' | COLOR_PAIR(GNT_COLOR_NORMAL)); + wbkgdset(widget->window, '\0' | gnt_color_pair(GNT_COLOR_NORMAL)); while (i < widget->priv.height - pos) { mvwhline(widget->window, i, pos, ' ', @@ -576,22 +577,22 @@ position += pos + start + 1; mvwvline(widget->window, pos + start + 1, scrcol, - ' ' | COLOR_PAIR(GNT_COLOR_NORMAL), rows); + ' ' | gnt_color_pair(GNT_COLOR_NORMAL), rows); mvwvline(widget->window, position, scrcol, - ACS_CKBOARD | COLOR_PAIR(GNT_COLOR_HIGHLIGHT_D), showing); + ACS_CKBOARD | gnt_color_pair(GNT_COLOR_HIGHLIGHT_D), showing); } mvwaddch(widget->window, start + pos, scrcol, ((tree->top != tree->root) ? ACS_UARROW : ' ') | - COLOR_PAIR(GNT_COLOR_HIGHLIGHT_D)); + gnt_color_pair(GNT_COLOR_HIGHLIGHT_D)); mvwaddch(widget->window, widget->priv.height - pos - 1, scrcol, - (row ? ACS_DARROW : ' ') | COLOR_PAIR(GNT_COLOR_HIGHLIGHT_D)); + (row ? ACS_DARROW : ' ') | gnt_color_pair(GNT_COLOR_HIGHLIGHT_D)); /* If there's a search-text, show it in the bottom of the tree */ if (tree->priv->search && tree->priv->search->len > 0) { const char *str = gnt_util_onscreen_width_to_pointer(tree->priv->search->str, scrcol - 1, NULL); - wbkgdset(widget->window, '\0' | COLOR_PAIR(GNT_COLOR_HIGHLIGHT_D)); + wbkgdset(widget->window, '\0' | gnt_color_pair(GNT_COLOR_HIGHLIGHT_D)); mvwaddnstr(widget->window, widget->priv.height - pos - 1, pos, tree->priv->search->str, str - tree->priv->search->str); } @@ -1328,7 +1329,6 @@ tree->list = g_list_insert(tree->list, key, position + 1); } } - redraw_tree(tree); return row; @@ -1492,7 +1492,8 @@ col->text = g_strdup(text ? text : ""); } - if (get_distance(tree->top, row) >= 0 && get_distance(row, tree->bottom) >= 0) + if (GNT_WIDGET_IS_FLAG_SET(GNT_WIDGET(tree), GNT_WIDGET_MAPPED) && + get_distance(tree->top, row) >= 0 && get_distance(row, tree->bottom) >= 0) redraw_tree(tree); } } @@ -1776,6 +1777,8 @@ break; } } + if (GNT_WIDGET_IS_FLAG_SET(GNT_WIDGET(tree), GNT_WIDGET_MAPPED)) + readjust_columns(tree); } void gnt_tree_set_column_resizable(GntTree *tree, int col, gboolean res)
--- a/finch/libgnt/gnttree.h Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gnttree.h Sat Nov 17 02:20:01 2007 +0000 @@ -383,6 +383,8 @@ * * @see gnt_tree_set_column_titles * @see gnt_tree_set_show_title + * + * @since 2.0.0 (gnt), 2.1.0 (pidgin) */ void gnt_tree_set_column_title(GntTree *tree, int index, const char *title); @@ -486,6 +488,8 @@ * * @see gnt_tree_set_col_width * @see gnt_tree_set_column_width_ratio + * + * @since 2.0.0 (gnt), 2.1.0 (pidgin) */ void gnt_tree_set_column_resizable(GntTree *tree, int col, gboolean res); @@ -505,6 +509,8 @@ * @param tree The tree * @param col The index of the column * @param right @c TRUE if the text in the column should be right aligned + * + * @since 2.0.0 (gnt), 2.1.0 (pidgin) */ void gnt_tree_set_column_is_right_aligned(GntTree *tree, int col, gboolean right); @@ -519,6 +525,8 @@ * * @see gnt_tree_set_col_width * @see gnt_tree_set_column_resizable + * + * @since 2.0.0 (gnt), 2.1.0 (pidgin) */ void gnt_tree_set_column_width_ratio(GntTree *tree, int cols[]); @@ -527,6 +535,8 @@ * * @param tree The tree * @param col The index of the column + * + * @since 2.0.0 (gnt), 2.1.0 (pidgin) */ void gnt_tree_set_search_column(GntTree *tree, int col); @@ -535,6 +545,8 @@ * * @param tree The tree * @return @c TRUE if the user is searching, @c FALSE otherwise. + * + * @since 2.0.0 (gnt), 2.1.0 (pidgin) */ gboolean gnt_tree_is_searching(GntTree *tree); @@ -547,6 +559,8 @@ * string and the content of row in the search column. * If the function returns @c TRUE, the row is dislayed, * otherwise it's not. + * + * @since 2.0.0 (gnt), 2.1.0 (pidgin) */ void gnt_tree_set_search_function(GntTree *tree, gboolean (*func)(GntTree *tree, gpointer key, const char *search, const char *current));
--- a/finch/libgnt/gntutils.h Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gntutils.h Sat Nov 17 02:20:01 2007 +0000 @@ -139,6 +139,8 @@ * @param string The XHTML string * @param tv The GntTextView * @return @c TRUE if the string was added to the textview properly, @c FALSE otherwise. + * + * @since 2.2.0 */ gboolean gnt_util_parse_xhtml_to_textview(const char *string, GntTextView *tv); @@ -148,6 +150,8 @@ * @param widget The widget * @param key The key to trigger the button * @param button The button to trigger + * + * @since 2.0.0 (gnt), 2.1.0 (pidgin) */ -void gnt_util_set_trigger_widget(GntWidget *wid, const char *text, GntWidget *button); +void gnt_util_set_trigger_widget(GntWidget *widget, const char *key, GntWidget *button);
--- a/finch/libgnt/gntwidget.c Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gntwidget.c Sat Nov 17 02:20:01 2007 +0000 @@ -420,7 +420,7 @@ gnt_widget_hide(GntWidget *widget) { g_signal_emit(widget, signals[SIG_HIDE], 0); - wbkgdset(widget->window, '\0' | COLOR_PAIR(GNT_COLOR_NORMAL)); + wbkgdset(widget->window, '\0' | gnt_color_pair(GNT_COLOR_NORMAL)); #if 0 /* XXX: I have no clue why, but this seemed to be necessary. */ if (gnt_widget_has_shadow(widget)) @@ -466,7 +466,6 @@ *width = wid->priv.width + shadow; if (height) *height = wid->priv.height + shadow; - } static void @@ -477,31 +476,31 @@ if (!gnt_widget_has_shadow(widget)) shadow = FALSE; - wbkgd(widget->window, COLOR_PAIR(GNT_COLOR_NORMAL)); + wbkgd(widget->window, gnt_color_pair(GNT_COLOR_NORMAL)); werase(widget->window); if (!(GNT_WIDGET_FLAGS(widget) & GNT_WIDGET_NO_BORDER)) { /* - This is ugly. */ /* - What's your point? */ - mvwvline(widget->window, 0, 0, ACS_VLINE | COLOR_PAIR(GNT_COLOR_NORMAL), widget->priv.height); + mvwvline(widget->window, 0, 0, ACS_VLINE | gnt_color_pair(GNT_COLOR_NORMAL), widget->priv.height); mvwvline(widget->window, 0, widget->priv.width - 1, - ACS_VLINE | COLOR_PAIR(GNT_COLOR_NORMAL), widget->priv.height); + ACS_VLINE | gnt_color_pair(GNT_COLOR_NORMAL), widget->priv.height); mvwhline(widget->window, widget->priv.height - 1, 0, - ACS_HLINE | COLOR_PAIR(GNT_COLOR_NORMAL), widget->priv.width); - mvwhline(widget->window, 0, 0, ACS_HLINE | COLOR_PAIR(GNT_COLOR_NORMAL), widget->priv.width); - mvwaddch(widget->window, 0, 0, ACS_ULCORNER | COLOR_PAIR(GNT_COLOR_NORMAL)); + ACS_HLINE | gnt_color_pair(GNT_COLOR_NORMAL), widget->priv.width); + mvwhline(widget->window, 0, 0, ACS_HLINE | gnt_color_pair(GNT_COLOR_NORMAL), widget->priv.width); + mvwaddch(widget->window, 0, 0, ACS_ULCORNER | gnt_color_pair(GNT_COLOR_NORMAL)); mvwaddch(widget->window, 0, widget->priv.width - 1, - ACS_URCORNER | COLOR_PAIR(GNT_COLOR_NORMAL)); + ACS_URCORNER | gnt_color_pair(GNT_COLOR_NORMAL)); mvwaddch(widget->window, widget->priv.height - 1, 0, - ACS_LLCORNER | COLOR_PAIR(GNT_COLOR_NORMAL)); + ACS_LLCORNER | gnt_color_pair(GNT_COLOR_NORMAL)); mvwaddch(widget->window, widget->priv.height - 1, widget->priv.width - 1, - ACS_LRCORNER | COLOR_PAIR(GNT_COLOR_NORMAL)); + ACS_LRCORNER | gnt_color_pair(GNT_COLOR_NORMAL)); } if (shadow) { - wbkgdset(widget->window, '\0' | COLOR_PAIR(GNT_COLOR_SHADOW)); + wbkgdset(widget->window, '\0' | gnt_color_pair(GNT_COLOR_SHADOW)); mvwvline(widget->window, 1, widget->priv.width, ' ', widget->priv.height); mvwhline(widget->window, widget->priv.height, 1, ' ', widget->priv.width); } @@ -617,7 +616,7 @@ return; while (widget->parent) widget = widget->parent; - + if (!g_object_get_data(G_OBJECT(widget), "gnt:queue_update")) { int id = g_timeout_add(0, update_queue_callback, widget);
--- a/finch/libgnt/gntwidget.h Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gntwidget.h Sat Nov 17 02:20:01 2007 +0000 @@ -140,167 +140,176 @@ G_BEGIN_DECLS /** - * - * - * @return + * @return GType for GntWidget. */ GType gnt_widget_get_gtype(void); /** - * - * @param widget + * Destroy a widget. + * @param widget The widget to destroy. */ void gnt_widget_destroy(GntWidget *widget); /** - * - * @param widget + * Show a widget. This should only be used for toplevel widgets. For the rest + * of the widgets, use #gnt_widget_draw instead. + * + * @param widget The widget to show. */ void gnt_widget_show(GntWidget *widget); /** - * - * @param widget + * Draw a widget. + * @param widget The widget to draw. */ void gnt_widget_draw(GntWidget *widget); /** - * - * @param widget - * @param x - * @param y - * @param width - * @param height + * @internal + * Expose part of a widget. */ void gnt_widget_expose(GntWidget *widget, int x, int y, int width, int height); /** - * - * @param widget + * Hide a widget. + * @param widget The widget to hide. */ void gnt_widget_hide(GntWidget *widget); /** - * - * @param widget - * @param x - * @param y + * Get the position of a widget. + * + * @param widget The widget. + * @param x Location to store the x-coordinate of the widget. + * @param y Location to store the y-coordinate of the widget. */ void gnt_widget_get_position(GntWidget *widget, int *x, int *y); /** - * - * @param widget - * @param x - * @param y + * Set the position of a widget. + * @param widget The widget to reposition. + * @param x The x-coordinate of the widget. + * @param y The x-coordinate of the widget. */ void gnt_widget_set_position(GntWidget *widget, int x, int y); /** - * - * @param widget + * Request a widget to calculate its desired size. + * @param widget The widget. */ void gnt_widget_size_request(GntWidget *widget); /** - * - * @param widget - * @param width - * @param height + * Get the size of a widget. + * @param widget The widget. + * @param width Location to store the width of the widget. + * @param height Location to store the height of the widget. */ void gnt_widget_get_size(GntWidget *widget, int *width, int *height); /** - * - * @param widget - * @param width - * @param height + * Set the size of a widget. * - * @return + * @param widget The widget to resize. + * @param width The width of the widget. + * @param height The height of the widget. + * + * @return If the widget was resized to the new size. */ gboolean gnt_widget_set_size(GntWidget *widget, int width, int height); /** - * - * @param widget - * @param width - * @param height + * Confirm a requested a size for a widget. * - * @return + * @param widget The widget. + * @param width The requested width. + * @param height The requested height. + * + * @return @c TRUE if the new size was confirmed, @c FALSE otherwise. */ gboolean gnt_widget_confirm_size(GntWidget *widget, int width, int height); /** - * - * @param widget - * @param keys + * Trigger the key-press callbacks for a widget. * - * @return + * @param widget The widget. + * @param keys The keypress on the widget. + * + * @return @c TRUE if the key-press was handled, @c FALSE otherwise. */ gboolean gnt_widget_key_pressed(GntWidget *widget, const char *keys); /** - * - * @param widget - * @param event - * @param x - * @param y + * Trigger the 'click' callback of a widget. * - * @return + * @param widget The widget. + * @param event The mouseevent. + * @param x The x-coordinate of the mouse. + * @param y The y-coordinate of the mouse. + * + * @return @c TRUE if the event was handled, @c FALSE otherwise. */ gboolean gnt_widget_clicked(GntWidget *widget, GntMouseEvent event, int x, int y); /** - * - * @param widget - * @param set + * Give or remove focus to a widget. + * @param widget The widget. + * @param set @c TRUE of focus should be given to the widget, @c FALSE if + * focus should be removed. * - * @return + * @return @c TRUE if the focus has been changed, @c FALSE otherwise. */ gboolean gnt_widget_set_focus(GntWidget *widget, gboolean set); /** - * - * @param widget + * Activate a widget. This only applies to widgets that can be activated (eg. GntButton) + * @param widget The widget to activate. */ void gnt_widget_activate(GntWidget *widget); /** - * - * @param widget - * @param name + * Set the name of a widget. + * @param widget The widget. + * @param name A new name for the widget. */ void gnt_widget_set_name(GntWidget *widget, const char *name); +/** + * Get the name of a widget. + * @param widget The widget. + * @return The name of the widget. + */ const char *gnt_widget_get_name(GntWidget *widget); -/* Widget-subclasses should call this from the draw-callback. - * Applications should just call gnt_widget_draw instead of this. */ /** - * - * @param widget + * @internal + * Use #gnt_widget_draw instead. */ void gnt_widget_queue_update(GntWidget *widget); /** - * - * @param widget - * @param set + * Set whether a widget can take focus or not. + * + * @param widget The widget. + * @param set @c TRUE if the widget can take focus. */ void gnt_widget_set_take_focus(GntWidget *widget, gboolean set); /** - * - * @param widget - * @param set + * Set the visibility of a widget. + * + * @param widget The widget. + * @param set Whether the widget is visible or not. */ void gnt_widget_set_visible(GntWidget *widget, gboolean set); /** - * - * @param widget + * Check whether the widget has shadows. * - * @return + * @param widget The widget. + * + * @return @c TRUE if the widget has shadows. This checks both the user-setting + * and whether the widget can have shadows at all. */ gboolean gnt_widget_has_shadow(GntWidget *widget);
--- a/finch/libgnt/gntwindow.c Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gntwindow.c Sat Nov 17 02:20:01 2007 +0000 @@ -28,6 +28,7 @@ struct _GntWindowPriv { GHashTable *accels; /* key => menuitem-id */ + GntWindowFlags flags; }; enum @@ -189,16 +190,33 @@ if (name && window->priv) { if (!gnt_style_read_menu_accels(name, window->priv->accels)) { g_hash_table_destroy(window->priv->accels); - g_free(window->priv); - window->priv = NULL; + window->priv->accels = NULL; } } } const char * gnt_window_get_accel_item(GntWindow *window, const char *key) { - if (window->priv) + if (window->priv->accels) return g_hash_table_lookup(window->priv->accels, key); return NULL; } +void gnt_window_set_maximize(GntWindow *window, GntWindowFlags maximize) +{ + if (maximize & GNT_WINDOW_MAXIMIZE_X) + window->priv->flags |= GNT_WINDOW_MAXIMIZE_X; + else + window->priv->flags &= ~GNT_WINDOW_MAXIMIZE_X; + + if (maximize & GNT_WINDOW_MAXIMIZE_Y) + window->priv->flags |= GNT_WINDOW_MAXIMIZE_Y; + else + window->priv->flags &= ~GNT_WINDOW_MAXIMIZE_Y; +} + +GntWindowFlags gnt_window_get_maximize(GntWindow *window) +{ + return (window->priv->flags & (GNT_WINDOW_MAXIMIZE_X | GNT_WINDOW_MAXIMIZE_Y)); +} +
--- a/finch/libgnt/gntwindow.h Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gntwindow.h Sat Nov 17 02:20:01 2007 +0000 @@ -48,6 +48,12 @@ typedef struct _GntWindowPriv GntWindowPriv; typedef struct _GntWindowClass GntWindowClass; +typedef enum +{ + GNT_WINDOW_MAXIMIZE_X = 1 << 0, + GNT_WINDOW_MAXIMIZE_Y = 1 << 1, +} GntWindowFlags; + struct _GntWindow { GntBox parent; @@ -68,9 +74,7 @@ G_BEGIN_DECLS /** - * - * - * @return + * @return GType for GntWindow. */ GType gnt_window_get_gtype(void); @@ -78,30 +82,63 @@ #define gnt_hwindow_new(homo) gnt_window_box_new(homo, FALSE) /** - * + * Create a new window. * - * @return + * @return The newly created window. */ GntWidget * gnt_window_new(void); /** - * - * @param homo - * @param vert + * Create a new window. * - * @return + * @param homo @c TRUE if the widgets inside the window should have the same dimensions. + * @param vert @c TRUE if the widgets inside the window should be stacked vertically. + * + * @return The newly created window. */ GntWidget * gnt_window_box_new(gboolean homo, gboolean vert); /** - * - * @param window - * @param menu + * Set the menu for a window. + * + * @param window The window. + * @param menu The menu for the window. */ void gnt_window_set_menu(GntWindow *window, GntMenu *menu); +/** + * Return the id of a menuitem specified to a keystroke. + * + * @param window The window. + * @param key The keystroke. + * + * @return The id of the menuitem bound to the keystroke, or @c NULL. + * + * @since 2.3.0 + */ const char * gnt_window_get_accel_item(GntWindow *window, const char *key); +/** + * Maximize a window, either horizontally or vertically, or both. + * + * @param window The window to maximize. + * @param maximize The maximization state of the window. + * + * @since 2.3.0 + */ +void gnt_window_set_maximize(GntWindow *window, GntWindowFlags maximize); + +/** + * Get the maximization state of a window. + * + * @param window The window. + * + * @return The maximization state of the window. + * + * @since 2.3.0 + */ +GntWindowFlags gnt_window_get_maximize(GntWindow *window); + void gnt_window_workspace_hiding(GntWindow *); void gnt_window_workspace_showing(GntWindow *);
--- a/finch/libgnt/gntwm.c Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gntwm.c Sat Nov 17 02:20:01 2007 +0000 @@ -109,12 +109,10 @@ gnt_wm_copy_win(GntWidget *widget, GntNode *node) { WINDOW *src, *dst; - int shadow; if (!node) return; src = widget->window; dst = node->window; - shadow = gnt_widget_has_shadow(widget) ? 1 : 0; copywin(src, dst, node->scroll, 0, 0, 0, getmaxy(dst) - 1, getmaxx(dst) - 1, 0); } @@ -226,17 +224,22 @@ } static gboolean -sanitize_position(GntWidget *widget, int *x, int *y) +sanitize_position(GntWidget *widget, int *x, int *y, gboolean m) { int X_MAX = getmaxx(stdscr); int Y_MAX = getmaxy(stdscr) - 1; int w, h; int nx, ny; gboolean changed = FALSE; + GntWindowFlags flags = GNT_IS_WINDOW(widget) ? + gnt_window_get_maximize(GNT_WINDOW(widget)) : 0; gnt_widget_get_size(widget, &w, &h); if (x) { - if (*x + w > X_MAX) { + if (m && (flags & GNT_WINDOW_MAXIMIZE_X) && *x != 0) { + *x = 0; + changed = TRUE; + } else if (*x + w > X_MAX) { nx = MAX(0, X_MAX - w); if (nx != *x) { *x = nx; @@ -245,7 +248,10 @@ } } if (y) { - if (*y + h > Y_MAX) { + if (m && (flags & GNT_WINDOW_MAXIMIZE_Y) && *y != 0) { + *y = 0; + changed = TRUE; + } else if (*y + h > Y_MAX) { ny = MAX(0, Y_MAX - h); if (ny != *y) { *y = ny; @@ -257,7 +263,7 @@ } static void -refresh_node(GntWidget *widget, GntNode *node, gpointer null) +refresh_node(GntWidget *widget, GntNode *node, gpointer m) { int x, y, w, h; int nw, nh; @@ -265,14 +271,28 @@ int X_MAX = getmaxx(stdscr); int Y_MAX = getmaxy(stdscr) - 1; + GntWindowFlags flags = 0; + + if (m && GNT_IS_WINDOW(widget)) { + flags = gnt_window_get_maximize(GNT_WINDOW(widget)); + } + gnt_widget_get_position(widget, &x, &y); gnt_widget_get_size(widget, &w, &h); - if (sanitize_position(widget, &x, &y)) + if (sanitize_position(widget, &x, &y, !!m)) gnt_screen_move_widget(widget, x, y); - nw = MIN(w, X_MAX); - nh = MIN(h, Y_MAX); + if (flags & GNT_WINDOW_MAXIMIZE_X) + nw = X_MAX; + else + nw = MIN(w, X_MAX); + + if (flags & GNT_WINDOW_MAXIMIZE_Y) + nh = Y_MAX; + else + nh = MIN(h, Y_MAX); + if (nw != w || nh != h) gnt_screen_resize_widget(widget, nw, nh); } @@ -1004,9 +1024,9 @@ GntWM *wm = GNT_WM(bindable); endwin(); + refresh(); - g_hash_table_foreach(wm->nodes, (GHFunc)refresh_node, NULL); - refresh(); + g_hash_table_foreach(wm->nodes, (GHFunc)refresh_node, GINT_TO_POINTER(TRUE)); g_signal_emit(wm, signals[SIG_TERMINAL_REFRESH], 0); update_screen(wm); gnt_ws_draw_taskbar(wm->cws, TRUE); @@ -1046,7 +1066,7 @@ GntWM *wm = GNT_WM(wim); GntWidget *w = GNT_WIDGET(wid); wm->tagged = g_list_remove(wm->tagged, w); - mvwhline(w->window, 0, 1, ACS_HLINE | COLOR_PAIR(GNT_COLOR_NORMAL), 3); + mvwhline(w->window, 0, 1, ACS_HLINE | gnt_color_pair(GNT_COLOR_NORMAL), 3); gnt_widget_draw(w); } @@ -1066,7 +1086,7 @@ } wm->tagged = g_list_prepend(wm->tagged, widget); - wbkgdset(widget->window, ' ' | COLOR_PAIR(GNT_COLOR_HIGHLIGHT)); + wbkgdset(widget->window, ' ' | gnt_color_pair(GNT_COLOR_HIGHLIGHT)); mvwprintw(widget->window, 0, 1, "[T]"); gnt_widget_draw(widget); return TRUE; @@ -1609,7 +1629,7 @@ g_hash_table_replace(wm->nodes, widget, node); - refresh_node(widget, node, NULL); + refresh_node(widget, node, GINT_TO_POINTER(TRUE)); transient = !!GNT_WIDGET_IS_FLAG_SET(node->me, GNT_WIDGET_TRANSIENT); @@ -1622,13 +1642,11 @@ shadow = FALSE; x = widget->priv.x; y = widget->priv.y; - w = widget->priv.width; - h = widget->priv.height; + w = widget->priv.width + shadow; + h = widget->priv.height + shadow; - getmaxyx(stdscr, maxy, maxx); - maxy -= 1; /* room for the taskbar */ - maxy -= shadow; - maxx -= shadow; + maxx = getmaxx(stdscr); + maxy = getmaxy(stdscr) - 1; /* room for the taskbar */ x = MAX(0, x); y = MAX(0, y); @@ -1639,7 +1657,7 @@ w = MIN(w, maxx); h = MIN(h, maxy); - node->window = newwin(h + shadow, w + shadow, y, x); + node->window = newwin(h, w, y, x); gnt_wm_copy_win(widget, node); } #endif @@ -1675,7 +1693,7 @@ { while (widget->parent) widget = widget->parent; - + if (GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_INVISIBLE) || g_hash_table_lookup(wm->nodes, widget)) { update_screen(wm); @@ -1686,7 +1704,7 @@ const char *title = GNT_BOX(widget)->title; GntPosition *p = NULL; if (title && (p = g_hash_table_lookup(wm->positions, title)) != NULL) { - sanitize_position(widget, &p->x, &p->y); + sanitize_position(widget, &p->x, &p->y, TRUE); gnt_widget_set_position(widget, p->x, p->y); mvwin(widget->window, p->y, p->x); } @@ -1717,12 +1735,11 @@ void gnt_wm_window_close(GntWM *wm, GntWidget *widget) { GntWS *s; - GntNode *node; int pos; s = gnt_wm_widget_find_workspace(wm, widget); - if ((node = g_hash_table_lookup(wm->nodes, widget)) == NULL) + if (g_hash_table_lookup(wm->nodes, widget) == NULL) return; g_signal_emit(wm, signals[SIG_CLOSE_WIN], 0, widget); @@ -1859,8 +1876,11 @@ GntMenu *menu = GNT_WINDOW(win)->menu; if (menu) { const char *id = gnt_window_get_accel_item(GNT_WINDOW(win), keys); - if (id) - ret = (gnt_menu_get_item(menu, id) != NULL); + if (id) { + GntMenuItem *item = gnt_menu_get_item(menu, id); + if (item) + ret = gnt_menuitem_activate(item); + } } } if (!ret) @@ -1885,9 +1905,8 @@ { gboolean ret = TRUE; GntNode *node; - int shadow; int maxx, maxy; - + while (widget->parent) widget = widget->parent; node = g_hash_table_lookup(wm->nodes, widget); @@ -1901,9 +1920,8 @@ gnt_widget_set_size(widget, width, height); gnt_widget_draw(widget); - shadow = gnt_widget_has_shadow(widget) ? 1 : 0; - maxx = getmaxx(stdscr) - shadow; - maxy = getmaxy(stdscr) - 1 - shadow; + maxx = getmaxx(stdscr); + maxy = getmaxy(stdscr) - 1; height = MIN(height, maxy); width = MIN(width, maxx); wresize(node->window, height, width);
--- a/finch/libgnt/gntwm.h Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gntwm.h Sat Nov 17 02:20:01 2007 +0000 @@ -113,7 +113,7 @@ * whether to give focus to a new window. */ gboolean event_stack; - + GntKeyPressMode mode; GHashTable *positions; @@ -184,108 +184,149 @@ G_BEGIN_DECLS /** - * - * - * @return + * @return GType for GntWM. */ GType gnt_wm_get_gtype(void); +/** + * Add a workspace. + * @param wm The window-manager. + * @param ws The workspace to add. + */ void gnt_wm_add_workspace(GntWM *wm, GntWS *ws); +/** + * Switch to a workspace. + * @param wm The window-manager. + * @param n Index of the workspace to switch to. + * + * @return @c TRUE if the switch was successful. + */ gboolean gnt_wm_switch_workspace(GntWM *wm, gint n); + +/** + * Switch to the previous workspace from the current one. + * @param wm The window-manager. + */ gboolean gnt_wm_switch_workspace_prev(GntWM *wm); + +/** + * Switch to the next workspace from the current one. + * @param wm The window-manager. + */ gboolean gnt_wm_switch_workspace_next(GntWM *wm); + +/** + * Move a window to a specific workspace. + * @param wm The window manager. + * @param neww The new workspace. + * @param widget The widget to move. + */ void gnt_wm_widget_move_workspace(GntWM *wm, GntWS *neww, GntWidget *widget); + +/** + * Set the list of workspaces . + * @param wm The window manager. + * @param workspaces The list of workspaces. + */ void gnt_wm_set_workspaces(GntWM *wm, GList *workspaces); + +/** + * Find the workspace that contains a specific widget. + * @param wm The window-manager. + * @param widget The widget to find. + * @return The workspace that has the widget. + */ GntWS *gnt_wm_widget_find_workspace(GntWM *wm, GntWidget *widget); /** - * - * @param wm - * @param widget + * Process a new window. + * + * @param wm The window-manager. + * @param widget The new window. */ void gnt_wm_new_window(GntWM *wm, GntWidget *widget); /** - * - * @param wm - * @param widget + * Decorate a window. + * @param wm The window-manager. + * @param widget The widget to decorate. */ void gnt_wm_window_decorate(GntWM *wm, GntWidget *widget); /** - * - * @param wm - * @param widget + * Close a window. + * @param wm The window-manager. + * @param widget The window to close. */ void gnt_wm_window_close(GntWM *wm, GntWidget *widget); /** - * - * @param wm - * @param string + * Process input. * - * @return + * @param wm The window-manager. + * @param string The input string to process. + * + * @return @c TRUE of the string was processed, @c FALSE otherwise. */ gboolean gnt_wm_process_input(GntWM *wm, const char *string); /** - * - * @param wm - * @param event - * @param x - * @param y - * @param widget + * Process a click event. + * @param wm The window manager. + * @param event The mouse event. + * @param x The x-coordinate of the mouse. + * @param y The y-coordinate of the mouse. + * @param widget The widget under the mouse. * - * @return + * @return @c TRUE if the event was handled, @c FALSE otherwise. */ gboolean gnt_wm_process_click(GntWM *wm, GntMouseEvent event, int x, int y, GntWidget *widget); /** - * - * @param wm - * @param widget - * @param width - * @param height + * Resize a window. + * @param wm The window manager. + * @param widget The window to resize. + * @param width The desired width of the window. + * @param height The desired height of the window. */ void gnt_wm_resize_window(GntWM *wm, GntWidget *widget, int width, int height); /** - * - * @param wm - * @param widget - * @param x - * @param y + * Move a window. + * @param wm The window manager. + * @param widget The window to move. + * @param x The desired x-coordinate of the window. + * @param y The desired y-coordinate of the window. */ void gnt_wm_move_window(GntWM *wm, GntWidget *widget, int x, int y); /** - * - * @param wm - * @param widget + * Update a window. + * @param wm The window-manager. + * @param widget The window to update. */ void gnt_wm_update_window(GntWM *wm, GntWidget *widget); /** - * - * @param wm - * @param widget + * Raise a window. + * @param wm The window-manager. + * @param widget The window to raise. */ void gnt_wm_raise_window(GntWM *wm, GntWidget *widget); /** - * - * @param wm - * @param set + * @internal */ void gnt_wm_set_event_stack(GntWM *wm, gboolean set); +/** + * @internal + */ void gnt_wm_copy_win(GntWidget *widget, GntNode *node); /** - * - * - * @return + * @return The idle time of the user. */ time_t gnt_wm_get_idle_time(void);
--- a/finch/libgnt/gntws.c Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gntws.c Sat Nov 17 02:20:01 2007 +0000 @@ -45,7 +45,7 @@ mvwin(taskbar, Y_MAX, 0); } - wbkgdset(taskbar, '\0' | COLOR_PAIR(GNT_COLOR_NORMAL)); + wbkgdset(taskbar, '\0' | gnt_color_pair(GNT_COLOR_NORMAL)); werase(taskbar); n = g_list_length(ws->list); @@ -66,15 +66,15 @@ } else { color = GNT_COLOR_NORMAL; } - wbkgdset(taskbar, '\0' | COLOR_PAIR(color)); + wbkgdset(taskbar, '\0' | gnt_color_pair(color)); if (iter->next) - mvwhline(taskbar, 0, width * i, ' ' | COLOR_PAIR(color), width); + mvwhline(taskbar, 0, width * i, ' ' | gnt_color_pair(color), width); else - mvwhline(taskbar, 0, width * i, ' ' | COLOR_PAIR(color), getmaxx(stdscr) - width * i); + mvwhline(taskbar, 0, width * i, ' ' | gnt_color_pair(color), getmaxx(stdscr) - width * i); title = GNT_BOX(w)->title; mvwprintw(taskbar, 0, width * i, "%s", title ? title : "<gnt>"); if (i) - mvwaddch(taskbar, 0, width *i - 1, ACS_VLINE | A_STANDOUT | COLOR_PAIR(GNT_COLOR_NORMAL)); + mvwaddch(taskbar, 0, width *i - 1, ACS_VLINE | A_STANDOUT | gnt_color_pair(GNT_COLOR_NORMAL)); } wrefresh(taskbar); }
--- a/finch/libgnt/gntws.h Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/gntws.h Sat Nov 17 02:20:01 2007 +0000 @@ -42,7 +42,7 @@ struct _GntWS { GntBindable inherit; - gchar *name; + char *name; GList *list; GList *ordered; gpointer ui_data; @@ -69,18 +69,112 @@ G_BEGIN_DECLS +/** + * @return The GType for GntWS. + * + * @since 2.0.0 (gnt), 2.1.0 (pidgin) + */ GType gnt_ws_get_gtype(void); +/** + * Create a new workspace with the specified name. + * + * @param name The desired name of the workspace, or @c NULL. + * + * @return The newly created workspace. + * + * @since 2.0.0 (gnt), 2.1.0 (pidgin) + */ GntWS *gnt_ws_new(const char *name); + +/** + * Set the name of a workspace. + * + * @param ws The workspace to rename. + * @param name The new name of the workspace. + * + * @since 2.0.0 (gnt), 2.1.0 (pidgin) + */ void gnt_ws_set_name(GntWS *ws, const gchar *name); + +/** + * Add a widget to a workspace. + * + * @param ws The workspace. + * @param widget The widget to add. + * + * @since 2.0.0 (gnt), 2.1.0 (pidgin) + */ void gnt_ws_add_widget(GntWS *ws, GntWidget *widget); + +/** + * Remove a widget from a workspace. + * + * @param ws The workspace + * @param widget The widget to remove from the workspace. + * + * @since 2.0.0 (gnt), 2.1.0 (pidgin) + */ void gnt_ws_remove_widget(GntWS *ws, GntWidget *widget); + +/** + * Hide a widget in a workspace. + * + * @param widget The widget to hide. + * @param nodes A hashtable containing information about the widgets. + * + * @since 2.0.0 (gnt), 2.1.0 (pidgin) + */ void gnt_ws_widget_hide(GntWidget *widget, GHashTable *nodes); + +/** + * Show a widget in a workspace. + * + * @param widget The widget to show. + * @param nodes A hashtable containing information about the widgets. + * + * @since 2.0.0 (gnt), 2.1.0 (pidgin) + */ void gnt_ws_widget_show(GntWidget *widget, GHashTable *nodes); + +/** + * Draw the taskbar in a workspace. + * + * @param ws The workspace. + * @param reposition Whether the workspace should reposition the taskbar. + * + * @since 2.0.0 (gnt), 2.1.0 (pidgin) + */ void gnt_ws_draw_taskbar(GntWS *ws, gboolean reposition); + +/** + * Hide a workspace. + * + * @param ws The workspace to hide. + * @param table A hashtable containing information about the widgets. + * + * @since 2.0.0 (gnt), 2.1.0 (pidgin) + */ void gnt_ws_hide(GntWS *ws, GHashTable *table); + +/** + * Show a workspace. + * + * @param ws The workspace to hide. + * @param table A hashtable containing information about the widgets. + * + * @since 2.0.0 (gnt), 2.1.0 (pidgin) + */ void gnt_ws_show(GntWS *ws, GHashTable *table); +/** + * Get the name of a workspace. + * + * @param ws The workspace. + * @return The name of the workspace (can be @c NULL). + * + * @since 2.0.0 (gnt), 2.1.0 (pidgin) + */ const char * gnt_ws_get_name(GntWS *ws); #endif
--- a/finch/libgnt/test/Makefile Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/libgnt/test/Makefile Sat Nov 17 02:20:01 2007 +0000 @@ -1,5 +1,5 @@ CC=gcc -CFLAGS=`pkg-config --cflags gobject-2.0 gmodule-2.0` -g -I../ -DSTANDALONE +CFLAGS=`pkg-config --cflags gobject-2.0 gmodule-2.0` -g -I../ -DSTANDALONE -I/usr/include/ncursesw/ LDFLAGS=`pkg-config --libs gobject-2.0 gmodule-2.0 gnt` -pg EXAMPLES=combo focus tv multiwin keys menu parse
--- a/finch/plugins/gntclipboard.c Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/plugins/gntclipboard.c Sat Nov 17 02:20:01 2007 +0000 @@ -156,7 +156,7 @@ PURPLE_PRIORITY_DEFAULT, "gntclipboard", N_("GntClipboard"), - VERSION, + DISPLAY_VERSION, N_("Clipboard plugin"), N_("When the gnt clipboard contents change, " "the contents are made available to X, if possible."),
--- a/finch/plugins/gntgf.c Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/plugins/gntgf.c Sat Nov 17 02:20:01 2007 +0000 @@ -375,7 +375,7 @@ PURPLE_PRIORITY_DEFAULT, "gntgf", N_("GntGf"), - VERSION, + DISPLAY_VERSION, N_("Toaster plugin"), N_("Toaster plugin"), "Sadrul H Chowdhury <sadrul@users.sourceforge.net>",
--- a/finch/plugins/gnthistory.c Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/plugins/gnthistory.c Sat Nov 17 02:20:01 2007 +0000 @@ -25,13 +25,14 @@ #include "conversation.h" #include "debug.h" #include "log.h" -#include "notify.h" +#include "request.h" #include "prefs.h" #include "signals.h" #include "util.h" #include "version.h" #include "gntplugin.h" +#include "gntrequest.h" #define HISTORY_PLUGIN_ID "gnt-history" @@ -135,10 +136,50 @@ if (!purple_prefs_get_bool("/purple/logging/log_ims") && !purple_prefs_get_bool("/purple/logging/log_chats")) { - purple_notify_warning(plugin, NULL, _("History Plugin Requires Logging"), - _("Logging can be enabled from Tools -> Preferences -> Logging.\n\n" - "Enabling logs for instant messages and/or chats will activate " - "history for the same conversation type(s).")); + PurpleRequestFields *fields = purple_request_fields_new(); + PurpleRequestFieldGroup *group; + PurpleRequestField *field; + struct { + const char *pref; + const char *label; + } prefs[] = { + {"/purple/logging/log_ims", N_("Log IMs")}, + {"/purple/logging/log_chats", N_("Log chats")}, + {NULL, NULL} + }; + int iter; + GList *list = purple_log_logger_get_options(); + const char *system = purple_prefs_get_string("/purple/logging/format"); + + group = purple_request_field_group_new(_("Logging")); + + field = purple_request_field_list_new("/purple/logging/format", _("Log format")); + while (list) { + const char *label = _(list->data); + list = g_list_delete_link(list, list); + purple_request_field_list_add(field, label, list->data); + if (system && strcmp(system, list->data) == 0) + purple_request_field_list_add_selected(field, label); + list = g_list_delete_link(list, list); + } + purple_request_field_group_add_field(group, field); + + for (iter = 0; prefs[iter].pref; iter++) { + field = purple_request_field_bool_new(prefs[iter].pref, _(prefs[iter].label), + purple_prefs_get_bool(prefs[iter].pref)); + purple_request_field_group_add_field(group, field); + } + + purple_request_fields_add_group(fields, group); + + purple_request_fields(plugin, NULL, _("History Plugin Requires Logging"), + _("Logging can be enabled from Tools -> Preferences -> Logging.\n\n" + "Enabling logs for instant messages and/or chats will activate " + "history for the same conversation type(s)."), + fields, + _("OK"), G_CALLBACK(finch_request_save_in_prefs), + _("Cancel"), NULL, + NULL, NULL, NULL, plugin); } } @@ -177,7 +218,7 @@ PURPLE_PRIORITY_DEFAULT, HISTORY_PLUGIN_ID, N_("GntHistory"), - VERSION, + DISPLAY_VERSION, N_("Shows recently logged conversations in new conversations."), N_("When a new conversation is opened this plugin will insert " "the last conversation into the current conversation."),
--- a/finch/plugins/lastlog.c Sat Nov 17 02:19:52 2007 +0000 +++ b/finch/plugins/lastlog.c Sat Nov 17 02:20:01 2007 +0000 @@ -120,7 +120,7 @@ PURPLE_PRIORITY_DEFAULT, "gntlastlog", N_("GntLastlog"), - VERSION, + DISPLAY_VERSION, N_("Lastlog plugin."), N_("Lastlog plugin."), "Sadrul H Chowdhury <sadrul@users.sourceforge.net>",
--- a/libpurple/Makefile.am Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/Makefile.am Sat Nov 17 02:20:01 2007 +0000 @@ -6,6 +6,7 @@ purple-send \ purple-send-async \ purple-url-handler \ + purple.h.in \ purple.pc.in \ purple-uninstalled.pc.in \ version.h.in \ @@ -114,7 +115,6 @@ privacy.h \ proxy.h \ prpl.h \ - purple.h \ request.h \ roomlist.h \ savedstatuses.h \ @@ -130,11 +130,12 @@ upnp.h \ util.h \ value.h \ - version.h \ xmlnode.h \ whiteboard.h -BUILT_SOURCES = version.h +purple_builtheaders = purple.h version.h + +BUILT_SOURCES = $(purple_builtheaders) if ENABLE_DBUS @@ -157,7 +158,8 @@ connection.h conversation.h core.h ft.h log.h notify.h prefs.h roomlist.h \ savedstatuses.h status.h server.h util.h xmlnode.h -purple_build_coreheaders = $(addprefix $(srcdir)/, $(purple_coreheaders)) +purple_build_coreheaders = $(addprefix $(srcdir)/, $(purple_coreheaders)) \ + $(purple_builtheaders) dbus_build_exported = $(addprefix $(srcdir)/, $(dbus_exported)) dbus-types.c: dbus-analyze-types.py $(purple_build_coreheaders) @@ -184,7 +186,7 @@ purple-client-bindings.c: dbus-analyze-functions.py $(dbus_exported) cat $(dbus_build_exported) | $(PYTHON) $(srcdir)/dbus-analyze-functions.py --client > $@ -purple-client-bindings.h: dbus-analyze-types.py dbus-analyze-functions.py $(purple_coreheaders) $(dbus_exported) +purple-client-bindings.h: dbus-analyze-types.py dbus-analyze-functions.py $(purple_coreheaders) $(purple_builtheaders) $(dbus_exported) cat $(purple_build_coreheaders) | $(PYTHON) $(srcdir)/dbus-analyze-types.py --keyword=enum --verbatim > $@ cat $(dbus_build_exported) | $(PYTHON) $(srcdir)/dbus-analyze-functions.py --client --headers >> $@ @@ -223,6 +225,7 @@ libpurpleincludedir=$(includedir)/libpurple libpurpleinclude_HEADERS = \ $(purple_coreheaders) \ + $(purple_builtheaders) \ $(dbus_headers) libpurple_la_DEPENDENCIES = $(STATIC_LINK_LIBS)
--- a/libpurple/Makefile.mingw Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/Makefile.mingw Sat Nov 17 02:20:01 2007 +0000 @@ -119,7 +119,7 @@ sed -e 's/@PURPLE_VERSION@/$(PURPLE_VERSION)/g' \ $@.in > $@ -$(OBJECTS): $(PURPLE_CONFIG_H) $(PURPLE_VERSION_H) +$(OBJECTS): $(PURPLE_CONFIG_H) $(PURPLE_VERSION_H) $(PURPLE_PURPLE_H) $(TARGET).dll $(TARGET).dll.a: $(OBJECTS) $(CC) -shared $(OBJECTS) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -Wl,--output-def,$(TARGET).def,--out-implib,$(TARGET).dll.a -o $(TARGET).dll
--- a/libpurple/account.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/account.c Sat Nov 17 02:20:01 2007 +0000 @@ -41,6 +41,14 @@ #include "util.h" #include "xmlnode.h" +typedef struct +{ + PurpleConnectionErrorInfo *current_error; +} PurpleAccountPrivate; + +#define PURPLE_ACCOUNT_GET_PRIVATE(account) \ + ((PurpleAccountPrivate *) (account->priv)) + /* TODO: Should use PurpleValue instead of this? What about "ui"? */ typedef struct { @@ -52,7 +60,7 @@ { int integer; char *string; - gboolean bool; + gboolean boolean; } value; @@ -77,6 +85,9 @@ static GList *handles = NULL; +static void set_current_error(PurpleAccount *account, + PurpleConnectionErrorInfo *new_err); + /********************************************************************* * Writing to disk * *********************************************************************/ @@ -107,7 +118,7 @@ } else if (setting->type == PURPLE_PREF_BOOLEAN) { xmlnode_set_attrib(child, "type", "bool"); - snprintf(buf, sizeof(buf), "%d", setting->value.bool); + snprintf(buf, sizeof(buf), "%d", setting->value.boolean); xmlnode_insert_data(child, buf, -1); } } @@ -310,8 +321,32 @@ } static xmlnode * +current_error_to_xmlnode(PurpleConnectionErrorInfo *err) +{ + xmlnode *node, *child; + char type_str[3]; + + node = xmlnode_new("current_error"); + + if(err == NULL) + return node; + + child = xmlnode_new_child(node, "type"); + snprintf(type_str, sizeof(type_str), "%u", err->type); + xmlnode_insert_data(child, type_str, -1); + + child = xmlnode_new_child(node, "description"); + if(err->description) + xmlnode_insert_data(child, err->description, -1); + + return node; +} + +static xmlnode * account_to_xmlnode(PurpleAccount *account) { + PurpleAccountPrivate *priv = PURPLE_ACCOUNT_GET_PRIVATE(account); + xmlnode *node, *child; const char *tmp; PurplePresence *presence; @@ -368,6 +403,9 @@ xmlnode_insert_child(node, child); } + child = current_error_to_xmlnode(priv->current_error); + xmlnode_insert_child(node, child); + return node; } @@ -672,6 +710,42 @@ purple_account_set_proxy_info(account, proxy_info); } +static void +parse_current_error(xmlnode *node, PurpleAccount *account) +{ + guint type; + char *type_str = NULL, *description = NULL; + xmlnode *child; + PurpleConnectionErrorInfo *current_error = NULL; + + child = xmlnode_get_child(node, "type"); + if (child == NULL || (type_str = xmlnode_get_data(child)) == NULL) + return; + type = atoi(type_str); + g_free(type_str); + + if (type > PURPLE_CONNECTION_ERROR_OTHER_ERROR) + { + purple_debug_error("account", + "Invalid PurpleConnectionError value %d found when " + "loading account information for %s\n", + type, purple_account_get_username(account)); + type = PURPLE_CONNECTION_ERROR_OTHER_ERROR; + } + + child = xmlnode_get_child(node, "description"); + if (child) + description = xmlnode_get_data(child); + if (description == NULL) + description = g_strdup(""); + + current_error = g_new0(PurpleConnectionErrorInfo, 1); + current_error->type = type; + current_error->description = description; + + set_current_error(account, current_error); +} + static PurpleAccount * parse_account(xmlnode *node) { @@ -781,6 +855,13 @@ parse_proxy_info(child, ret); } + /* Read current error */ + child = xmlnode_get_child(node, "current_error"); + if (child != NULL) + { + parse_current_error(child, ret); + } + return ret; } @@ -827,6 +908,7 @@ purple_account_new(const char *username, const char *protocol_id) { PurpleAccount *account = NULL; + PurpleAccountPrivate *priv = NULL; PurplePlugin *prpl = NULL; PurplePluginProtocolInfo *prpl_info = NULL; PurpleStatusType *status_type; @@ -841,6 +923,8 @@ account = g_new0(PurpleAccount, 1); PURPLE_DBUS_REGISTER_POINTER(account, PurpleAccount); + priv = g_new0(PurpleAccountPrivate, 1); + account->priv = priv; purple_account_set_username(account, username); @@ -881,6 +965,7 @@ void purple_account_destroy(PurpleAccount *account) { + PurpleAccountPrivate *priv = NULL; GList *l; g_return_if_fail(account != NULL); @@ -912,6 +997,10 @@ if(account->system_log) purple_log_free(account->system_log); + priv = PURPLE_ACCOUNT_GET_PRIVATE(account); + g_free(priv->current_error); + g_free(priv); + PURPLE_DBUS_UNREGISTER_POINTER(account); g_free(account); } @@ -1620,7 +1709,7 @@ setting = g_new0(PurpleAccountSetting, 1); setting->type = PURPLE_PREF_BOOLEAN; - setting->value.bool = value; + setting->value.boolean = value; g_hash_table_insert(account->settings, g_strdup(name), setting); @@ -1706,7 +1795,7 @@ setting->type = PURPLE_PREF_BOOLEAN; setting->ui = g_strdup(ui); - setting->value.bool = value; + setting->value.boolean = value; table = get_ui_settings_table(account, ui); @@ -1981,7 +2070,7 @@ g_return_val_if_fail(setting->type == PURPLE_PREF_BOOLEAN, default_value); - return setting->value.bool; + return setting->value.boolean; } int @@ -2047,7 +2136,7 @@ g_return_val_if_fail(setting->type == PURPLE_PREF_BOOLEAN, default_value); - return setting->value.bool; + return setting->value.boolean; } PurpleLog * @@ -2214,6 +2303,65 @@ return prpl_info->offline_message(buddy); } +static void +signed_on_cb(PurpleConnection *gc, + gpointer unused) +{ + PurpleAccount *account = purple_connection_get_account(gc); + purple_account_clear_current_error(account); +} + +static void +set_current_error(PurpleAccount *account, + PurpleConnectionErrorInfo *new_err) +{ + PurpleAccountPrivate *priv = PURPLE_ACCOUNT_GET_PRIVATE(account); + PurpleConnectionErrorInfo *old_err = priv->current_error; + + if(new_err == old_err) + return; + + priv->current_error = new_err; + + purple_signal_emit(purple_accounts_get_handle(), + "account-error-changed", + account, old_err, new_err); + schedule_accounts_save(); + + if(old_err) + g_free(old_err->description); + + g_free(old_err); +} + +static void +connection_error_cb(PurpleConnection *gc, + PurpleConnectionError type, + const gchar *description, + gpointer unused) +{ + PurpleAccount *account = purple_connection_get_account(gc); + PurpleConnectionErrorInfo *err = g_new0(PurpleConnectionErrorInfo, 1); + + err->type = type; + err->description = g_strdup(description); + + set_current_error(account, err); +} + +const PurpleConnectionErrorInfo * +purple_account_get_current_error(PurpleAccount *account) +{ + PurpleAccountPrivate *priv = PURPLE_ACCOUNT_GET_PRIVATE(account); + return priv->current_error; +} + +void +purple_account_clear_current_error(PurpleAccount *account) +{ + set_current_error(account, NULL); +} + void purple_accounts_add(PurpleAccount *account) { @@ -2238,6 +2386,11 @@ schedule_accounts_save(); + /* Clearing the error ensures that account-error-changed is emitted, + * which is the end of the guarantee that the the error's pointer is + * valid. + */ + purple_account_clear_current_error(account); purple_signal_emit(purple_accounts_get_handle(), "account-removed", account); } @@ -2443,6 +2596,7 @@ purple_accounts_init(void) { void *handle = purple_accounts_get_handle(); + void *conn_handle = purple_connections_get_handle(); purple_signal_register(handle, "account-connecting", purple_marshal_VOID__POINTER, NULL, 1, @@ -2513,6 +2667,19 @@ PURPLE_SUBTYPE_ACCOUNT), purple_value_new(PURPLE_TYPE_STRING)); + purple_signal_register(handle, "account-error-changed", + purple_marshal_VOID__POINTER_POINTER_POINTER, + NULL, 3, + purple_value_new(PURPLE_TYPE_SUBTYPE, + PURPLE_SUBTYPE_ACCOUNT), + purple_value_new(PURPLE_TYPE_POINTER), + purple_value_new(PURPLE_TYPE_POINTER)); + + purple_signal_connect(conn_handle, "signed-on", handle, + PURPLE_CALLBACK(signed_on_cb), NULL); + purple_signal_connect(conn_handle, "connection-error", handle, + PURPLE_CALLBACK(connection_error_cb), NULL); + load_accounts(); } @@ -2520,6 +2687,7 @@ void purple_accounts_uninit(void) { + gpointer handle = purple_accounts_get_handle(); if (save_timer != 0) { purple_timeout_remove(save_timer); @@ -2527,5 +2695,6 @@ sync_accounts(); } - purple_signals_unregister_by_instance(purple_accounts_get_handle()); + purple_signals_disconnect_by_handle(handle); + purple_signals_unregister_by_instance(handle); }
--- a/libpurple/account.h Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/account.h Sat Nov 17 02:20:01 2007 +0000 @@ -1,6 +1,7 @@ /** * @file account.h Account API * @ingroup core + * @see @ref account-signals */ /* purple @@ -22,8 +23,6 @@ * You 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 - * - * @see @ref account-signals */ #ifndef _PURPLE_ACCOUNT_H_ #define _PURPLE_ACCOUNT_H_ @@ -141,6 +140,8 @@ void *ui_data; /**< The UI can put data here. */ PurpleAccountRegistrationCb registration_cb; void *registration_cb_user_data; + + gpointer priv; /**< Pointer to opaque private data. */ }; #ifdef __cplusplus @@ -894,6 +895,25 @@ */ gboolean purple_account_supports_offline_message(PurpleAccount *account, PurpleBuddy *buddy); +/** + * Get the error that caused the account to be disconnected, or @c NULL if the + * account is happily connected or disconnected without an error. + * + * @param account The account whose error should be retrieved. + * @constreturn The type of error and a human-readable description of the + * current error, or @c NULL if there is no current error. This + * pointer is guaranteed to remain valid until the @ref + * account-error-changed signal is emitted for @a account. + */ +const PurpleConnectionErrorInfo *purple_account_get_current_error(PurpleAccount *account); + +/** + * Clear an account's current error state, resetting it to @c NULL. + * + * @param account The account whose error state should be cleared. + */ +void purple_account_clear_current_error(PurpleAccount *account); + /*@}*/ /**************************************************************************/
--- a/libpurple/blist.h Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/blist.h Sat Nov 17 02:20:01 2007 +0000 @@ -1,6 +1,7 @@ /** * @file blist.h Buddy List API * @ingroup core + * @see @ref blist-signals */ /* purple @@ -22,8 +23,6 @@ * You 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 - * - * @see @ref blist-signals */ #ifndef _PURPLE_BLIST_H_ #define _PURPLE_BLIST_H_ @@ -41,8 +40,6 @@ typedef struct _PurpleContact PurpleContact; typedef struct _PurpleBuddy PurpleBuddy; -typedef gboolean (*PurpleFilterBlistFunc)(PurpleBlistNode *node); - /**************************************************************************/ /* Enumerations */ /**************************************************************************/ @@ -68,12 +65,9 @@ typedef enum { PURPLE_BLIST_NODE_FLAG_NO_SAVE = 1 << 0, /**< node should not be saved with the buddy list */ - PURPLE_BLIST_NODE_HAS_CONVERSATION = 1 << 1, /**< node (buddy or chat) has an open conversation */ } PurpleBlistNodeFlags; -#define PURPLE_BLIST_NODE_SET_FLAG(node, f) (((PurpleBlistNode *)node)->flags |= (f)) -#define PURPLE_BLIST_NODE_UNSET_FLAG(node, f) (((PurpleBlistNode *)node)->flags &= ~(f)) #define PURPLE_BLIST_NODE_HAS_FLAG(b, f) (((PurpleBlistNode*)(b))->flags & (f)) #define PURPLE_BLIST_NODE_SHOULD_SAVE(b) (! PURPLE_BLIST_NODE_HAS_FLAG(b, PURPLE_BLIST_NODE_FLAG_NO_SAVE)) @@ -488,6 +482,7 @@ */ PurpleBuddy *purple_contact_get_priority_buddy(PurpleContact *contact); +#ifndef PURPLE_DISABLE_DEPRECATED /** * Sets the alias for a contact. * @@ -497,6 +492,7 @@ * @deprecated Use purple_blist_alias_contact() instead. */ void purple_contact_set_alias(PurpleContact *contact, const char *alias); +#endif /** * Gets the alias for a contact. @@ -523,8 +519,11 @@ * @param contact The contact */ void purple_contact_invalidate_priority_buddy(PurpleContact *contact); + /** * Removes a buddy from the buddy list and frees the memory allocated to it. + * This doesn't actually try to remove the buddy from the server list, nor does + * it clean up the prpl_data. * * @param buddy The buddy to be removed */
--- a/libpurple/buddyicon.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/buddyicon.c Sat Nov 17 02:20:01 2007 +0000 @@ -116,7 +116,7 @@ { purple_debug_error("buddyicon", "Unable to create directory %s: %s\n", - dirname, strerror(errno)); + dirname, g_strerror(errno)); } } @@ -125,7 +125,7 @@ if (!fwrite(purple_imgstore_get_data(img), purple_imgstore_get_size(img), 1, file)) { purple_debug_error("buddyicon", "Error writing %s: %s\n", - path, strerror(errno)); + path, g_strerror(errno)); } else purple_debug_info("buddyicon", "Wrote cache file: %s\n", path); @@ -135,7 +135,7 @@ else { purple_debug_error("buddyicon", "Unable to create file %s: %s\n", - path, strerror(errno)); + path, g_strerror(errno)); g_free(path); return; } @@ -163,7 +163,7 @@ if (g_unlink(path)) { purple_debug_error("buddyicon", "Failed to delete %s: %s\n", - path, strerror(errno)); + path, g_strerror(errno)); } else { @@ -505,37 +505,33 @@ purple_buddy_icon_set_data(icon, icon_data, icon_len, checksum); else if (icon_data && icon_len > 0) { - if (icon_data != NULL && icon_len > 0) - { - PurpleBuddyIcon *icon = purple_buddy_icon_new(account, username, icon_data, icon_len, checksum); + PurpleBuddyIcon *icon = purple_buddy_icon_new(account, username, icon_data, icon_len, checksum); - /* purple_buddy_icon_new() calls - * purple_buddy_icon_set_data(), which calls - * purple_buddy_icon_update(), which has the buddy list - * and conversations take references as appropriate. - * This function doesn't return icon, so we can't - * leave a reference dangling. */ - purple_buddy_icon_unref(icon); - } - else + /* purple_buddy_icon_new() calls + * purple_buddy_icon_set_data(), which calls + * purple_buddy_icon_update(), which has the buddy list + * and conversations take references as appropriate. + * This function doesn't return icon, so we can't + * leave a reference dangling. */ + purple_buddy_icon_unref(icon); + } + else + { + /* If the buddy list or a conversation was holding a + * reference, we'd have found the icon in the cache. + * Since we know we're deleting the icon, we only + * need a subset of purple_buddy_icon_update(). */ + + GSList *buddies = purple_find_buddies(account, username); + while (buddies != NULL) { - /* If the buddy list or a conversation was holding a - * reference, we'd have found the icon in the cache. - * Since we know we're deleting the icon, we only - * need a subset of purple_buddy_icon_update(). */ + PurpleBuddy *buddy = (PurpleBuddy *)buddies->data; - GSList *buddies = purple_find_buddies(account, username); - while (buddies != NULL) - { - PurpleBuddy *buddy = (PurpleBuddy *)buddies->data; + unref_filename(purple_blist_node_get_string((PurpleBlistNode *)buddy, "buddy_icon")); + purple_blist_node_remove_setting((PurpleBlistNode *)buddy, "buddy_icon"); + purple_blist_node_remove_setting((PurpleBlistNode *)buddy, "icon_checksum"); - unref_filename(purple_blist_node_get_string((PurpleBlistNode *)buddy, "buddy_icon")); - purple_blist_node_remove_setting((PurpleBlistNode *)buddy, "buddy_icon"); - purple_blist_node_remove_setting((PurpleBlistNode *)buddy, "icon_checksum"); - - buddies = g_slist_delete_link(buddies, buddies); - } - + buddies = g_slist_delete_link(buddies, buddies); } } } @@ -955,7 +951,7 @@ if (!fwrite(icon_data, icon_len, 1, file)) { purple_debug_error("buddyicon", "Error writing %s: %s\n", - path, strerror(errno)); + path, g_strerror(errno)); } else purple_debug_info("buddyicon", "Wrote migrated cache file: %s\n", path); @@ -965,7 +961,7 @@ else { purple_debug_error("buddyicon", "Unable to create file %s: %s\n", - path, strerror(errno)); + path, g_strerror(errno)); g_free(new_filename); g_free(path); @@ -1060,7 +1056,7 @@ { purple_debug_error("buddyicon", "Unable to create directory %s: %s\n", - dirname, strerror(errno)); + dirname, g_strerror(errno)); } } }
--- a/libpurple/certificate.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/certificate.c Sat Nov 17 02:20:01 2007 +0000 @@ -88,6 +88,19 @@ g_return_if_fail(vrq); + if (st == PURPLE_CERTIFICATE_VALID) { + purple_debug_info("certificate", + "Successfully verified certificate for %s\n", + vrq->subject_name); + } else { + purple_debug_info("certificate", + "Failed to verify certificate for %s\n", + vrq->subject_name); + } + + + + /* Pass the results on to the request's callback */ (vrq->cb)(st, vrq->cb_data); @@ -1344,6 +1357,7 @@ /* Okay, we're done here */ purple_certificate_verify_complete(vrq, PURPLE_CERTIFICATE_INVALID); + return; } /* if (signature chain not good) */ /* Next, attempt to verify the last certificate against a CA */ @@ -1372,7 +1386,8 @@ 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) ) { + ca_crt = purple_certificate_pool_retrieve(ca, ca_id); + if ( NULL == ca_crt ) { purple_debug_info("certificate/x509/tls_cached", "Certificate Authority with DN='%s' not " "found. I'll prompt the user, I guess.\n", @@ -1385,16 +1400,7 @@ 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) ) {
--- a/libpurple/certificate.h Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/certificate.h Sat Nov 17 02:20:01 2007 +0000 @@ -1,6 +1,8 @@ /** * @file certificate.h Public-Key Certificate API * @ingroup core + * @see @ref certificate-signals + * @since 2.2.0 */ /* @@ -433,7 +435,7 @@ * * @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.) + * @todo Find a way to give the reason (bad signature, not the issuer, etc.) */ gboolean purple_certificate_signed_by(PurpleCertificate *crt, PurpleCertificate *issuer); @@ -448,7 +450,7 @@ * @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 + * @todo Specify which certificate in the chain caused a failure */ gboolean purple_certificate_check_signature_chain(GList *chain); @@ -779,7 +781,7 @@ * 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 + * @todo Will break on CA certs, as they have no Common Name */ void purple_certificate_display_x509(PurpleCertificate *crt);
--- a/libpurple/cipher.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/cipher.c Sat Nov 17 02:20:01 2007 +0000 @@ -616,7 +616,7 @@ /* * The s-box values are permuted according to the 'primitive function P' */ -static guint32 sbox1[64] = +static const guint32 sbox1[64] = { 0x00808200, 0x00000000, 0x00008000, 0x00808202, 0x00808002, 0x00008202, 0x00000002, 0x00008000, 0x00000200, 0x00808200, 0x00808202, 0x00000200, 0x00800202, 0x00808002, 0x00800000, 0x00000002, @@ -628,7 +628,7 @@ 0x00000202, 0x00800200, 0x00800200, 0x00000000, 0x00008002, 0x00008200, 0x00000000, 0x00808002 }; -static guint32 sbox2[64] = +static const guint32 sbox2[64] = { 0x40084010, 0x40004000, 0x00004000, 0x00084010, 0x00080000, 0x00000010, 0x40080010, 0x40004010, 0x40000010, 0x40084010, 0x40084000, 0x40000000, 0x40004000, 0x00080000, 0x00000010, 0x40080010, @@ -640,7 +640,7 @@ 0x00084000, 0x00000000, 0x40004000, 0x00004010, 0x40000000, 0x40080010, 0x40084010, 0x00084000 }; -static guint32 sbox3[64] = +static const guint32 sbox3[64] = { 0x00000104, 0x04010100, 0x00000000, 0x04010004, 0x04000100, 0x00000000, 0x00010104, 0x04000100, 0x00010004, 0x04000004, 0x04000004, 0x00010000, 0x04010104, 0x00010004, 0x04010000, 0x00000104, @@ -652,7 +652,7 @@ 0x04010000, 0x04000104, 0x00000104, 0x04010000, 0x00010104, 0x00000004, 0x04010004, 0x00010100 }; -static guint32 sbox4[64] = +static const guint32 sbox4[64] = { 0x80401000, 0x80001040, 0x80001040, 0x00000040, 0x00401040, 0x80400040, 0x80400000, 0x80001000, 0x00000000, 0x00401000, 0x00401000, 0x80401040, 0x80000040, 0x00000000, 0x00400040, 0x80400000, @@ -664,7 +664,7 @@ 0x80001000, 0x00001040, 0x00400000, 0x80401000, 0x00000040, 0x00400000, 0x00001000, 0x00401040 }; -static guint32 sbox5[64] = +static const guint32 sbox5[64] = { 0x00000080, 0x01040080, 0x01040000, 0x21000080, 0x00040000, 0x00000080, 0x20000000, 0x01040000, 0x20040080, 0x00040000, 0x01000080, 0x20040080, 0x21000080, 0x21040000, 0x00040080, 0x20000000, @@ -676,7 +676,7 @@ 0x00040080, 0x01000080, 0x20000080, 0x00040000, 0x00000000, 0x20040000, 0x01040080, 0x20000080 }; -static guint32 sbox6[64] = +static const guint32 sbox6[64] = { 0x10000008, 0x10200000, 0x00002000, 0x10202008, 0x10200000, 0x00000008, 0x10202008, 0x00200000, 0x10002000, 0x00202008, 0x00200000, 0x10000008, 0x00200008, 0x10002000, 0x10000000, 0x00002008, @@ -688,7 +688,7 @@ 0x00002000, 0x00200008, 0x10002008, 0x00000000, 0x10202000, 0x10000000, 0x00200008, 0x10002008 }; -static guint32 sbox7[64] = +static const guint32 sbox7[64] = { 0x00100000, 0x02100001, 0x02000401, 0x00000000, 0x00000400, 0x02000401, 0x00100401, 0x02100400, 0x02100401, 0x00100000, 0x00000000, 0x02000001, 0x00000001, 0x02000000, 0x02100001, 0x00000401, @@ -700,7 +700,7 @@ 0x00000000, 0x00100401, 0x02100000, 0x00000400, 0x02000001, 0x02000400, 0x00000400, 0x00100001 }; -static guint32 sbox8[64] = +static const guint32 sbox8[64] = { 0x08000820, 0x00000800, 0x00020000, 0x08020820, 0x08000000, 0x08000820, 0x00000020, 0x08000000, 0x00020020, 0x08020000, 0x08020820, 0x00020800, 0x08020800, 0x00020820, 0x00000800, 0x00000020, @@ -718,7 +718,7 @@ * * These two tables are part of the 'permuted choice 1' function. * * In this implementation several speed improvements are done. * */ -static guint32 leftkey_swap[16] = +static const guint32 leftkey_swap[16] = { 0x00000000, 0x00000001, 0x00000100, 0x00000101, 0x00010000, 0x00010001, 0x00010100, 0x00010101, @@ -726,7 +726,7 @@ 0x01010000, 0x01010001, 0x01010100, 0x01010101 }; -static guint32 rightkey_swap[16] = +static const guint32 rightkey_swap[16] = { 0x00000000, 0x01000000, 0x00010000, 0x01010000, 0x00000100, 0x01000100, 0x00010100, 0x01010100, @@ -742,7 +742,7 @@ * ordering of the subkeys so we can omit the table for decryption * subkey schedule. */ -static guint8 encrypt_rotate_tab[16] = +static const guint8 encrypt_rotate_tab[16] = { 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 };
--- a/libpurple/cipher.h Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/cipher.h Sat Nov 17 02:20:01 2007 +0000 @@ -1,6 +1,7 @@ /** * @file cipher.h Purple Cipher API * @ingroup core + * @see @ref cipher-signals */ /* purple @@ -22,8 +23,6 @@ * You 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 - * - * @see @ref cipher-signals */ #ifndef PURPLE_CIPHER_H #define PURPLE_CIPHER_H
--- a/libpurple/connection.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/connection.c Sat Nov 17 02:20:01 2007 +0000 @@ -432,7 +432,7 @@ { g_return_val_if_fail(gc != NULL, NULL); - return gc->password; + return gc->password ? gc->password : gc->account->password; } const char * @@ -488,29 +488,120 @@ void purple_connection_error(PurpleConnection *gc, const char *text) { + /* prpls that have not been updated to use disconnection reasons will + * be setting wants_to_die before calling this function, so choose + * PURPLE_CONNECTION_ERROR_OTHER_ERROR (which is fatal) if it's true, + * and PURPLE_CONNECTION_ERROR_NETWORK_ERROR (which isn't) if not. See + * the documentation in connection.h. + */ + PurpleConnectionError reason = gc->wants_to_die + ? PURPLE_CONNECTION_ERROR_OTHER_ERROR + : PURPLE_CONNECTION_ERROR_NETWORK_ERROR; + purple_connection_error_reason (gc, reason, text); +} + +void +purple_connection_error_reason (PurpleConnection *gc, + PurpleConnectionError reason, + const char *description) +{ PurpleConnectionUiOps *ops; g_return_if_fail(gc != NULL); + /* This sanity check relies on PURPLE_CONNECTION_ERROR_OTHER_ERROR + * being the last member of the PurpleConnectionError enum in + * connection.h; if other reasons are added after it, this check should + * be updated. + */ + if (reason > PURPLE_CONNECTION_ERROR_OTHER_ERROR) { + purple_debug_error("connection", + "purple_connection_error_reason: reason %u isn't a " + "valid reason\n", reason); + reason = PURPLE_CONNECTION_ERROR_OTHER_ERROR; + } - if (text == NULL) { - purple_debug_error("connection", "purple_connection_error: check `text != NULL' failed\n"); - text = _("Unknown error"); + if (description == NULL) { + purple_debug_error("connection", "purple_connection_error_reason: check `description != NULL' failed\n"); + description = _("Unknown error"); } /* If we've already got one error, we don't need any more */ if (gc->disconnect_timeout) return; + gc->wants_to_die = purple_connection_error_is_fatal (reason); + ops = purple_connections_get_ui_ops(); - if (ops != NULL && ops->report_disconnect != NULL) - ops->report_disconnect(gc, text); + if (ops != NULL) + { + if (ops->report_disconnect_reason != NULL) + ops->report_disconnect_reason (gc, reason, description); + if (ops->report_disconnect != NULL) + ops->report_disconnect (gc, description); + } + + purple_signal_emit(purple_connections_get_handle(), "connection-error", + gc, reason, description); gc->disconnect_timeout = purple_timeout_add(0, purple_connection_disconnect_cb, purple_connection_get_account(gc)); } void +purple_connection_ssl_error (PurpleConnection *gc, + PurpleSslErrorType ssl_error) +{ + PurpleConnectionError reason; + + switch (ssl_error) { + case PURPLE_SSL_HANDSHAKE_FAILED: + case PURPLE_SSL_CONNECT_FAILED: + reason = PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR; + break; + case PURPLE_SSL_CERTIFICATE_INVALID: + /* TODO: maybe PURPLE_SSL_* should be more specific? */ + reason = PURPLE_CONNECTION_ERROR_CERT_OTHER_ERROR; + break; + default: + g_assert_not_reached (); + reason = PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR; + } + + purple_connection_error_reason (gc, reason, + purple_ssl_strerror(ssl_error)); +} + +gboolean +purple_connection_error_is_fatal (PurpleConnectionError reason) +{ + switch (reason) + { + case PURPLE_CONNECTION_ERROR_NETWORK_ERROR: + return FALSE; + case PURPLE_CONNECTION_ERROR_INVALID_USERNAME: + case PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED: + case PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE: + case PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT: + case PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR: + case PURPLE_CONNECTION_ERROR_NAME_IN_USE: + case PURPLE_CONNECTION_ERROR_INVALID_SETTINGS: + case PURPLE_CONNECTION_ERROR_OTHER_ERROR: + case PURPLE_CONNECTION_ERROR_CERT_NOT_PROVIDED: + case PURPLE_CONNECTION_ERROR_CERT_UNTRUSTED: + case PURPLE_CONNECTION_ERROR_CERT_EXPIRED: + case PURPLE_CONNECTION_ERROR_CERT_NOT_ACTIVATED: + case PURPLE_CONNECTION_ERROR_CERT_HOSTNAME_MISMATCH: + case PURPLE_CONNECTION_ERROR_CERT_FINGERPRINT_MISMATCH: + case PURPLE_CONNECTION_ERROR_CERT_SELF_SIGNED: + case PURPLE_CONNECTION_ERROR_CERT_OTHER_ERROR: + return TRUE; + default: + g_return_val_if_reached(TRUE); + } +} + +void purple_connections_disconnect_all(void) { GList *l; @@ -571,6 +662,14 @@ purple_marshal_VOID__POINTER, NULL, 1, purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION)); + + purple_signal_register(handle, "connection-error", + purple_marshal_VOID__POINTER_INT_POINTER, NULL, 3, + purple_value_new(PURPLE_TYPE_SUBTYPE, + PURPLE_SUBTYPE_CONNECTION), + purple_value_new(PURPLE_TYPE_ENUM), + purple_value_new(PURPLE_TYPE_STRING)); + } void
--- a/libpurple/connection.h Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/connection.h Sat Nov 17 02:20:01 2007 +0000 @@ -1,6 +1,7 @@ /** * @file connection.h Connection API * @ingroup core + * @see @ref connection-signals */ /* purple @@ -22,8 +23,6 @@ * You 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 - * - * @see @ref connection-signals */ #ifndef _PURPLE_CONNECTION_H_ #define _PURPLE_CONNECTION_H_ @@ -55,11 +54,97 @@ } PurpleConnectionState; +/** Possible errors that can cause a connection to be closed. + * @since 2.3.0 + */ +typedef enum +{ + /** There was an error sending or receiving on the network socket, or + * there was some protocol error (such as the server sending malformed + * data). + */ + PURPLE_CONNECTION_ERROR_NETWORK_ERROR = 0, + /** The username supplied was not valid. */ + PURPLE_CONNECTION_ERROR_INVALID_USERNAME = 1, + /** The username, password or some other credential was incorrect. Use + * #PURPLE_CONNECTION_ERROR_INVALID_USERNAME instead if the username + * is known to be invalid. + */ + PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED = 2, + /** libpurple doesn't speak any of the authentication methods the + * server offered. + */ + PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE = 3, + /** libpurple was built without SSL support, and the connection needs + * SSL. + */ + PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT = 4, + /** There was an error negotiating SSL on this connection, or the + * server does not support encryption but an account option was set to + * require it. + */ + PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR = 5, + /** Someone is already connected to the server using the name you are + * trying to connect with. + */ + PURPLE_CONNECTION_ERROR_NAME_IN_USE = 6, + + /** The username/server/other preference for the account isn't valid. + * For instance, on IRC the screen name cannot contain white space. + * This reason should not be used for incorrect passwords etc: use + * #PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED for that. + * + * @todo This reason really shouldn't be necessary. Usernames and + * other account preferences should be validated when the + * account is created. + */ + PURPLE_CONNECTION_ERROR_INVALID_SETTINGS = 7, + + /** The server did not provide a SSL certificate. */ + PURPLE_CONNECTION_ERROR_CERT_NOT_PROVIDED = 8, + /** The server's SSL certificate could not be trusted. */ + PURPLE_CONNECTION_ERROR_CERT_UNTRUSTED = 9, + /** The server's SSL certificate has expired. */ + PURPLE_CONNECTION_ERROR_CERT_EXPIRED = 10, + /** The server's SSL certificate is not yet valid. */ + PURPLE_CONNECTION_ERROR_CERT_NOT_ACTIVATED = 11, + /** The server's SSL certificate did not match its hostname. */ + PURPLE_CONNECTION_ERROR_CERT_HOSTNAME_MISMATCH = 12, + /** The server's SSL certificate does not have the expected + * fingerprint. + */ + PURPLE_CONNECTION_ERROR_CERT_FINGERPRINT_MISMATCH = 13, + /** The server's SSL certificate is self-signed. */ + PURPLE_CONNECTION_ERROR_CERT_SELF_SIGNED = 14, + /** There was some other error validating the server's SSL certificate. + */ + PURPLE_CONNECTION_ERROR_CERT_OTHER_ERROR = 15, + + /** Some other error occured which fits into none of the other + * categories. + */ + /* purple_connection_error_reason() in connection.c uses the fact that + * this is the last member of the enum when sanity-checking; if other + * reasons are added after it, the check must be updated. + */ + PURPLE_CONNECTION_ERROR_OTHER_ERROR = 16 +} PurpleConnectionError; + +/** Holds the type of an error along with its description. */ +typedef struct +{ + /** The type of error. */ + PurpleConnectionError type; + /** A localised, human-readable description of the error. */ + char *description; +} PurpleConnectionErrorInfo; + #include <time.h> #include "account.h" #include "plugin.h" #include "status.h" +#include "sslconn.h" /** Connection UI operations. Used to notify the user of changes to * connections, such as being disconnected, and to respond to the @@ -74,11 +159,13 @@ * the UI of what is happening, as well as which @a step out of @a * step_count has been reached (which might be displayed as a progress * bar). + * @see #purple_connection_update_progress */ void (*connect_progress)(PurpleConnection *gc, const char *text, size_t step, size_t step_count); + /** Called when a connection is established (just before the * @ref signed-on signal). */ @@ -87,17 +174,23 @@ * and @ref signed-off signals). */ void (*disconnected)(PurpleConnection *gc); + /** Used to display connection-specific notices. (Pidgin's Gtk user * interface implements this as a no-op; #purple_connection_notice(), * which uses this operation, is not used by any of the protocols * shipped with libpurple.) */ void (*notice)(PurpleConnection *gc, const char *text); + /** Called when an error causes a connection to be disconnected. * Called before #disconnected. * @param text a localized error message. + * @see #purple_connection_error + * @deprecated in favour of + * #PurpleConnectionUiOps.report_disconnect_reason. */ void (*report_disconnect)(PurpleConnection *gc, const char *text); + /** Called when libpurple discovers that the computer's network * connection is active. On Linux, this uses Network Manager if * available; on Windows, it uses Win32's network change notification @@ -109,10 +202,24 @@ */ void (*network_disconnected)(); + /** Called when an error causes a connection to be disconnected. + * Called before #disconnected. This op is intended to replace + * #report_disconnect. If both are implemented, this will be called + * first; however, there's no real reason to implement both. + * @param reason why the connection ended, if known, or + * #PURPLE_CONNECTION_ERROR_OTHER_ERROR, if not. + * @param text a localized message describing the disconnection + * in more detail to the user. + * @see #purple_connection_error_reason + * @since 2.3.0 + */ + void (*report_disconnect_reason)(PurpleConnection *gc, + PurpleConnectionError reason, + const char *text); + void (*_purple_reserved1)(void); void (*_purple_reserved2)(void); void (*_purple_reserved3)(void); - void (*_purple_reserved4)(void); } PurpleConnectionUiOps; struct _PurpleConnection @@ -126,19 +233,23 @@ char *password; /**< The password used. */ int inpa; /**< The input watcher. */ - GSList *buddy_chats; /**< A list of active chats. */ + GSList *buddy_chats; /**< A list of active chats + (#PurpleConversation structs of type + #PURPLE_CONV_TYPE_CHAT). */ void *proto_data; /**< Protocol-specific data. */ char *display_name; /**< How you appear to other people. */ guint keepalive; /**< Keep-alive. */ + /** Wants to Die state. This is set when the user chooses to log out, or + * when the protocol is disconnected and should not be automatically + * reconnected (incorrect password, etc.). prpls should rely on + * purple_connection_error_reason() to set this for them rather than + * setting it themselves. + * @see purple_connection_error_is_fatal + */ + gboolean wants_to_die; - gboolean wants_to_die; /**< Wants to Die state. This is set - when the user chooses to log out, - or when the protocol is - disconnected and should not be - automatically reconnected - (incorrect password, etc.) */ guint disconnect_timeout; /**< Timer used for nasty stack tricks */ }; @@ -151,6 +262,7 @@ /**************************************************************************/ /*@{*/ +#ifndef PURPLE_DISABLE_DEPRECATED /** * This function should only be called by purple_account_connect() * in account.c. If you're trying to sign on an account, use that @@ -167,10 +279,14 @@ * @param regist Whether we are registering a new account or just * trying to do a normal signon. * @param password The password to use. + * + * @deprecated As this is internal, we should make it private in 3.0.0. */ void purple_connection_new(PurpleAccount *account, gboolean regist, const char *password); +#endif +#ifndef PURPLE_DISABLE_DEPRECATED /** * This function should only be called by purple_account_unregister() * in account.c. @@ -180,9 +296,15 @@ * * @param account The account to unregister * @param password The password to use. + * @param cb Optional callback to be called when unregistration is complete + * @param user_data user data to pass to the callback + * + * @deprecated As this is internal, we should make it private in 3.0.0. */ void purple_connection_new_unregister(PurpleAccount *account, const char *password, PurpleAccountUnregistrationCb cb, void *user_data); +#endif +#ifndef PURPLE_DISABLE_DEPRECATED /** * Disconnects and destroys a PurpleConnection. * @@ -191,12 +313,15 @@ * function instead. * * @param gc The purple connection to destroy. + * + * @deprecated As this is internal, we should make it private in 3.0.0. */ void purple_connection_destroy(PurpleConnection *gc); +#endif /** * Sets the connection state. PRPLs should call this and pass in - * the state "PURPLE_CONNECTED" when the account is completely + * the state #PURPLE_CONNECTED when the account is completely * signed on. What does it mean to be completely signed on? If * the core can call prpl->set_status, and it successfully changes * your status, then the account is online. @@ -289,10 +414,65 @@ * Closes a connection with an error. * * @param gc The connection. - * @param reason The error text. + * @param reason The error text, which may not be @c NULL. + * @deprecated in favour of #purple_connection_error_reason. Calling + * @c purple_connection_error(gc, text) is equivalent to calling + * @c purple_connection_error_reason(gc, reason, text) where @c reason is + * #PURPLE_CONNECTION_ERROR_OTHER_ERROR if @c gc->wants_to_die is @c TRUE, and + * #PURPLE_CONNECTION_ERROR_NETWORK_ERROR if not. (This is to keep + * auto-reconnection behaviour the same when using old prpls which don't use + * reasons yet.) */ void purple_connection_error(PurpleConnection *gc, const char *reason); +/** + * Closes a connection with an error and a human-readable description of the + * error. It also sets @c gc->wants_to_die to the value of + * #purple_connection_error_is_fatal(@a reason), mainly for + * backwards-compatibility. + * + * @param gc the connection which is closing. + * @param reason why the connection is closing. + * @param description a non-@c NULL localized description of the error. + * @since 2.3.0 + */ +void +purple_connection_error_reason (PurpleConnection *gc, + PurpleConnectionError reason, + const char *description); + +/** + * Closes a connection due to an SSL error; this is basically a shortcut to + * turning the #PurpleSslErrorType into a #PurpleConnectionError and a + * human-readable string and then calling purple_connection_error_reason(). + * @since 2.3.0 + */ +void +purple_connection_ssl_error (PurpleConnection *gc, + PurpleSslErrorType ssl_error); + +/** + * Reports whether a disconnection reason is fatal (in which case the account + * should probably not be automatically reconnected) or transient (so + * auto-reconnection is a good idea). + * For instance, #PURPLE_CONNECTION_ERROR_NETWORK_ERROR is a temporary error, + * which might be caused by losing the network connection, so <tt> + * purple_connection_error_is_fatal (PURPLE_CONNECTION_ERROR_NETWORK_ERROR)</tt> + * is @c FALSE. On the other hand, + * #PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED probably indicates a + * misconfiguration of the account which needs the user to go fix it up, so + * <tt> purple_connection_error_is_fatal + * (PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED)</tt> is @c TRUE. + * + * (This function is meant to replace checking PurpleConnection.wants_to_die.) + * + * @return @c TRUE if the account should not be automatically reconnected, and + * @c FALSE otherwise. + * @since 2.3.0 + */ +gboolean +purple_connection_error_is_fatal (PurpleConnectionError reason); + /*@}*/ /**************************************************************************/
--- a/libpurple/conversation.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/conversation.c Sat Nov 17 02:20:01 2007 +0000 @@ -205,8 +205,8 @@ /* Functions that deal with PurpleConvMessage */ static void -add_message_to_history(PurpleConversation *conv, const char *who, const char *message, - PurpleMessageFlags flags, time_t when) +add_message_to_history(PurpleConversation *conv, const char *who, const char *alias, + const char *message, PurpleMessageFlags flags, time_t when) { PurpleConvMessage *msg; @@ -218,10 +218,11 @@ me = conv->account->username; who = me; } - + msg = g_new0(PurpleConvMessage, 1); PURPLE_DBUS_REGISTER_POINTER(msg, PurpleConvMessage); msg->who = g_strdup(who); + msg->alias = g_strdup(alias); msg->flags = flags; msg->what = g_strdup(message); msg->when = when; @@ -234,6 +235,7 @@ free_conv_message(PurpleConvMessage *msg) { g_free(msg->who); + g_free(msg->alias); g_free(msg->what); PURPLE_DBUS_UNREGISTER_POINTER(msg); g_free(msg); @@ -293,14 +295,10 @@ /* Check if this conversation already exists. */ if ((conv = purple_find_conversation_with_account(type, name, account)) != NULL) { - if (purple_conversation_get_type(conv) != PURPLE_CONV_TYPE_CHAT || - purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv))) - { - if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) - purple_conversation_chat_cleanup_for_rejoin(conv); - - return conv; - } + if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT && + purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv))) + purple_conversation_chat_cleanup_for_rejoin(conv); + return conv; } gc = purple_account_get_connection(account); @@ -934,7 +932,8 @@ if (ops && ops->write_conv) ops->write_conv(conv, who, alias, displayed, flags, mtime); - add_message_to_history(conv, who, message, flags, mtime); + + add_message_to_history(conv, who, alias, message, flags, mtime); purple_signal_emit(purple_conversations_get_handle(), (type == PURPLE_CONV_TYPE_IM ? "wrote-im-msg" : "wrote-chat-msg"),
--- a/libpurple/conversation.h Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/conversation.h Sat Nov 17 02:20:01 2007 +0000 @@ -1,6 +1,7 @@ /** * @file conversation.h Conversation API * @ingroup core + * @see @ref conversation-signals */ /* purple @@ -22,8 +23,6 @@ * You 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 - * - * @see @ref conversation-signals */ #ifndef _PURPLE_CONVERSATION_H_ #define _PURPLE_CONVERSATION_H_ @@ -172,9 +171,12 @@ void (*write_im)(PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime); - /** Write a message to a conversation. This is used rather than - * the chat- or im-specific ops for generic messages, such as system - * messages like "x is now know as y". + /** Write a message to a conversation. This is used rather than the + * chat- or im-specific ops for errors, system messages (such as "x is + * now know as y"), and as the fallback if #write_im and #write_chat + * are not implemented. It should be implemented, or the UI will miss + * conversation error messages and your users will hate you. + * * @see purple_conversation_write() */ void (*write_conv)(PurpleConversation *conv, @@ -286,6 +288,8 @@ /** * Description of a conversation message + * + * @since 2.2.0 */ struct _PurpleConvMessage { @@ -293,7 +297,8 @@ char *what; PurpleMessageFlags flags; time_t when; - PurpleConversation *conv; + PurpleConversation *conv; /**< @since 2.3.0 */ + char *alias; /**< @since 2.3.0 */ }; /** @@ -672,6 +677,8 @@ * @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. + * + * @since 2.2.0 */ GList *purple_conversation_get_message_history(PurpleConversation *conv); @@ -679,6 +686,8 @@ * Clear the message history of a conversation. * * @param conv The conversation + * + * @since 2.2.0 */ void purple_conversation_clear_message_history(PurpleConversation *conv); @@ -688,6 +697,8 @@ * @param msg A PurpleConvMessage * * @return The name of the sender of the message + * + * @since 2.2.0 */ const char *purple_conversation_message_get_sender(PurpleConvMessage *msg); @@ -697,6 +708,8 @@ * @param msg A PurpleConvMessage * * @return The name of the sender of the message + * + * @since 2.2.0 */ const char *purple_conversation_message_get_message(PurpleConvMessage *msg); @@ -706,6 +719,8 @@ * @param msg A PurpleConvMessage * * @return The name of the sender of the message + * + * @since 2.2.0 */ PurpleMessageFlags purple_conversation_message_get_flags(PurpleConvMessage *msg); @@ -715,6 +730,8 @@ * @param msg A PurpleConvMessage * * @return The name of the sender of the message + * + * @since 2.2.0 */ time_t purple_conversation_message_get_timestamp(PurpleConvMessage *msg); @@ -1030,7 +1047,7 @@ * * @param chat The chat. * - * @return The list of ignored users. + * @constreturn The list of ignored users. */ GList *purple_conv_chat_get_ignored(const PurpleConvChat *chat); @@ -1320,6 +1337,8 @@ * @return A list of PurpleMenuAction items, harvested by the * chat-extended-menu signal. The list and the menuaction * items should be freed by the caller. + * + * @since 2.1.0 */ GList * purple_conversation_get_extended_menu(PurpleConversation *conv); @@ -1333,6 +1352,8 @@ * message, if not @c NULL. It must be freed by the caller with g_free(). * * @return @c TRUE if the command was executed successfully, @c FALSE otherwise. + * + * @since 2.1.0 */ gboolean purple_conversation_do_command(PurpleConversation *conv, const gchar *cmdline, const gchar *markup, gchar **error);
--- a/libpurple/core.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/core.c Sat Nov 17 02:20:01 2007 +0000 @@ -94,6 +94,8 @@ /* The signals subsystem is important and should be first. */ purple_signals_init(); + purple_util_init(); + purple_signal_register(core, "uri-handler", purple_marshal_BOOLEAN__POINTER_POINTER_POINTER, purple_value_new(PURPLE_TYPE_BOOLEAN), 3, @@ -136,15 +138,17 @@ /* The buddy icon code uses the imgstore, so init it early. */ purple_imgstore_init(); - /* Accounts use status and buddy icons, so initialize these before accounts */ + /* Accounts use status, buddy icons and connection signals, so + * initialize these before accounts + */ purple_status_init(); purple_buddy_icons_init(); + purple_connections_init(); purple_accounts_init(); purple_savedstatuses_init(); purple_notify_init(); purple_certificate_init(); - purple_connections_init(); purple_conversations_init(); purple_blist_init(); purple_log_init(); @@ -229,6 +233,9 @@ #ifdef HAVE_DBUS purple_dbus_uninit(); #endif + + purple_util_uninit(); + purple_signals_uninit(); g_free(core->ui); @@ -378,7 +385,7 @@ if (g_rename(path, new_name)) { purple_debug_error("core", "Error renaming %s to %s: %s. Please report this at http://developer.pidgin.im\n", - path, new_name, strerror(errno)); + path, new_name, g_strerror(errno)); g_free(new_name); return FALSE; } @@ -391,7 +398,7 @@ if (symlink(new_name, old_name)) { purple_debug_warning("core", "Error symlinking %s to %s: %s. Please report this at http://developer.pidgin.im\n", - old_name, new_name, strerror(errno)); + old_name, new_name, g_strerror(errno)); } g_free(old_name); g_free(new_name); @@ -447,7 +454,7 @@ if (g_mkdir(user_dir, S_IRUSR | S_IWUSR | S_IXUSR) == -1) { purple_debug_error("core", "Error creating directory %s: %s. Please report this at http://developer.pidgin.im\n", - user_dir, strerror(errno)); + user_dir, g_strerror(errno)); g_free(status_file); g_free(old_user_dir); return FALSE; @@ -459,7 +466,7 @@ if (!(fp = g_fopen(status_file, "w"))) { purple_debug_error("core", "Error opening file %s for writing: %s. Please report this at http://developer.pidgin.im\n", - status_file, strerror(errno)); + status_file, g_strerror(errno)); g_free(status_file); g_free(old_user_dir); return FALSE; @@ -517,7 +524,7 @@ { char *name_utf8 = g_filename_to_utf8(name, -1, NULL, NULL, NULL); purple_debug_error("core", "Error reading symlink %s: %s. Please report this at http://developer.pidgin.im\n", - name_utf8, strerror(errno)); + name_utf8, g_strerror(errno)); g_free(name_utf8); g_free(name); g_dir_close(dir); @@ -555,7 +562,7 @@ if (symlink(link, logs_dir)) { purple_debug_error("core", "Error symlinking %s to %s: %s. Please report this at http://developer.pidgin.im\n", - logs_dir, link, strerror(errno)); + logs_dir, link, g_strerror(errno)); g_free(link); g_free(name); g_free(logs_dir); @@ -612,7 +619,7 @@ if (g_mkdir(new_icons_dir, S_IRUSR | S_IWUSR | S_IXUSR) == -1) { purple_debug_error("core", "Error creating directory %s: %s. Please report this at http://developer.pidgin.im\n", - new_icons_dir, strerror(errno)); + new_icons_dir, g_strerror(errno)); g_free(new_icons_dir); g_dir_close(icons_dir); g_free(name); @@ -675,7 +682,7 @@ if (!(fp = g_fopen(name, "rb"))) { purple_debug_error("core", "Error opening file %s for reading: %s. Please report this at http://developer.pidgin.im\n", - name, strerror(errno)); + name, g_strerror(errno)); g_free(name); g_dir_close(dir); g_free(status_file); @@ -687,7 +694,7 @@ if (!(new_file = g_fopen(new_name, "wb"))) { purple_debug_error("core", "Error opening file %s for writing: %s. Please report this at http://developer.pidgin.im\n", - new_name, strerror(errno)); + new_name, g_strerror(errno)); fclose(fp); g_free(new_name); g_free(name); @@ -706,7 +713,7 @@ if (size != sizeof(buf) && !feof(fp)) { purple_debug_error("core", "Error reading %s: %s. Please report this at http://developer.pidgin.im\n", - name, strerror(errno)); + name, g_strerror(errno)); fclose(new_file); fclose(fp); g_free(new_name); @@ -720,7 +727,7 @@ if (!fwrite(buf, size, 1, new_file) && ferror(new_file) != 0) { purple_debug_error("core", "Error writing %s: %s. Please report this at http://developer.pidgin.im\n", - new_name, strerror(errno)); + new_name, g_strerror(errno)); fclose(new_file); fclose(fp); g_free(new_name); @@ -735,12 +742,12 @@ if (fclose(new_file)) { purple_debug_error("core", "Error writing: %s: %s. Please report this at http://developer.pidgin.im\n", - new_name, strerror(errno)); + new_name, g_strerror(errno)); } if (fclose(fp)) { purple_debug_warning("core", "Error closing %s: %s\n", - name, strerror(errno)); + name, g_strerror(errno)); } g_free(new_name); } @@ -754,7 +761,7 @@ if (g_unlink(status_file)) { purple_debug_error("core", "Error unlinking file %s: %s. Please report this at http://developer.pidgin.im\n", - status_file, strerror(errno)); + status_file, g_strerror(errno)); g_free(status_file); return FALSE; }
--- a/libpurple/core.h Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/core.h Sat Nov 17 02:20:01 2007 +0000 @@ -1,5 +1,6 @@ /** * @defgroup core libpurple + * @see @ref core-signals */ /* purple @@ -21,8 +22,6 @@ * You 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 - * - * @see @ref core-signals */ #ifndef _PURPLE_CORE_H_ #define _PURPLE_CORE_H_
--- a/libpurple/dbus-analyze-functions.py Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/dbus-analyze-functions.py Sat Nov 17 02:20:01 2007 +0000 @@ -75,7 +75,11 @@ ] pointer = "#pointer#" -myexception = "My Exception" + +class MyException(Exception): + pass + +myexception = MyException() def ctopascal(name): newname = "" @@ -520,7 +524,7 @@ try: self.processfunction(functiontext, paramtexts) - except myexception: + except MyException: sys.stderr.write(myline + "\n") except: sys.stderr.write(myline + "\n")
--- a/libpurple/dbus-server.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/dbus-server.c Sat Nov 17 02:20:01 2007 +0000 @@ -674,6 +674,8 @@ int id; gint xint; guint xuint; + gint64 xint64; + guint64 xuint64; gboolean xboolean; gpointer ptr = NULL; gpointer val; @@ -694,6 +696,14 @@ xuint = my_arg(guint); dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &xuint); break; + case PURPLE_TYPE_INT64: + xint64 = my_arg(gint64); + dbus_message_iter_append_basic(iter, DBUS_TYPE_INT64, &xint64); + break; + case PURPLE_TYPE_UINT64: + xuint64 = my_arg(guint64); + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT64, &xuint64); + break; case PURPLE_TYPE_BOOLEAN: xboolean = my_arg(gboolean); dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &xboolean);
--- a/libpurple/dbus-server.h Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/dbus-server.h Sat Nov 17 02:20:01 2007 +0000 @@ -1,6 +1,7 @@ /** * @file dbus-server.h Purple DBUS Server * @ingroup core + * @see @ref dbus-server-signals */ /* purple @@ -22,8 +23,6 @@ * You 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 - * - * @see @ref dbus-server-signals */ #ifndef _PURPLE_DBUS_SERVER_H_
--- a/libpurple/desktopitem.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/desktopitem.c Sat Nov 17 02:20:01 2007 +0000 @@ -1155,7 +1155,7 @@ dfile = g_fopen(filename, "r"); if (!dfile) { - printf ("Can't open %s: %s", filename, strerror(errno)); + printf ("Can't open %s: %s", filename, g_strerror(errno)); return NULL; }
--- a/libpurple/dnsquery.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/dnsquery.c Sat Nov 17 02:20:01 2007 +0000 @@ -346,7 +346,7 @@ /* Create pipes for communicating with the child process */ if (pipe(child_out) || pipe(child_in)) { purple_debug_error("dns", - "Could not create pipes: %s\n", strerror(errno)); + "Could not create pipes: %s\n", g_strerror(errno)); return NULL; } @@ -374,7 +374,7 @@ if (resolver->dns_pid == -1) { purple_debug_error("dns", "Could not create child process for DNS: %s\n", - strerror(errno)); + g_strerror(errno)); purple_dnsquery_resolver_destroy(resolver); return NULL; } @@ -416,7 +416,7 @@ return FALSE; } else if (pid < 0) { purple_debug_warning("dns", "Wait for DNS child %d failed: %s\n", - resolver->dns_pid, strerror(errno)); + resolver->dns_pid, g_strerror(errno)); purple_dnsquery_resolver_destroy(resolver); return FALSE; } @@ -430,7 +430,7 @@ rc = write(resolver->fd_in, &dns_params, sizeof(dns_params)); if (rc < 0) { purple_debug_error("dns", "Unable to write to DNS child %d: %d\n", - resolver->dns_pid, strerror(errno)); + resolver->dns_pid, g_strerror(errno)); purple_dnsquery_resolver_destroy(resolver); return FALSE; } @@ -571,7 +571,7 @@ purple_dnsquery_resolved(query_data, hosts); } else if (rc == -1) { - g_snprintf(message, sizeof(message), _("Error reading from resolver process:\n%s"), strerror(errno)); + g_snprintf(message, sizeof(message), _("Error reading from resolver process:\n%s"), g_strerror(errno)); purple_dnsquery_failed(query_data, message); } else if (rc == 0) { @@ -637,9 +637,10 @@ static gboolean dns_main_thread_cb(gpointer data) { - PurpleDnsQueryData *query_data; + PurpleDnsQueryData *query_data = data; - query_data = data; + /* We're done, so purple_dnsquery_destroy() shouldn't think it is canceling an in-progress lookup */ + query_data->resolver = NULL; if (query_data->error_message != NULL) purple_dnsquery_failed(query_data, query_data->error_message); @@ -713,7 +714,7 @@ #endif /* back to main thread */ - g_idle_add(dns_main_thread_cb, query_data); + purple_timeout_add(0, dns_main_thread_cb, query_data); return 0; } @@ -780,14 +781,12 @@ purple_debug_info("dnsquery", "Performing DNS lookup for %s\n", hostname); - query_data = g_new(PurpleDnsQueryData, 1); + query_data = g_new0(PurpleDnsQueryData, 1); query_data->hostname = g_strdup(hostname); g_strstrip(query_data->hostname); query_data->port = port; query_data->callback = callback; query_data->data = data; - query_data->error_message = NULL; - query_data->hosts = NULL; if (strlen(query_data->hostname) == 0) {
--- a/libpurple/dnsquery.h Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/dnsquery.h Sat Nov 17 02:20:01 2007 +0000 @@ -59,7 +59,7 @@ PurpleDnsQueryFailedCallback failed_cb); /** Called just before @a query_data is freed; this should cancel any - * further use of @q query_data the UI would make. Unneeded if + * further use of @a query_data the UI would make. Unneeded if * #resolve_host is not implemented. */ void (*destroy)(PurpleDnsQueryData *query_data);
--- a/libpurple/eventloop.h Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/eventloop.h Sat Nov 17 02:20:01 2007 +0000 @@ -138,6 +138,8 @@ * @param data data to pass to @a function. * @return A handle to the timer which can be passed to * purple_timeout_remove to remove the timer. + * + * @since 2.1.0 */ guint purple_timeout_add_seconds(guint interval, GSourceFunc function, gpointer data);
--- a/libpurple/example/nullclient.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/example/nullclient.c Sat Nov 17 02:20:01 2007 +0000 @@ -269,6 +269,13 @@ PurpleAccount *account; PurpleSavedStatus *status; + /* libpurple's built-in DNS resolution forks processes to perform + * blocking lookups without blocking the main process. It does not + * handle SIGCHLD itself, so if the UI does not you quickly get an army + * of zombie subprocesses marching around. + */ + signal(SIGCHLD, SIG_IGN); + init_libpurple(); printf("libpurple initialized.\n");
--- a/libpurple/ft.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/ft.c Sat Nov 17 02:20:01 2007 +0000 @@ -207,15 +207,15 @@ switch(xfer_type) { case PURPLE_XFER_SEND: msg = g_strdup_printf(_("Error reading %s: \n%s.\n"), - utf8, strerror(err)); + utf8, g_strerror(err)); break; case PURPLE_XFER_RECEIVE: msg = g_strdup_printf(_("Error writing %s: \n%s.\n"), - utf8, strerror(err)); + utf8, g_strerror(err)); break; default: msg = g_strdup_printf(_("Error accessing %s: \n%s.\n"), - utf8, strerror(err)); + utf8, g_strerror(err)); break; } g_free(utf8);
--- a/libpurple/ft.h Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/ft.h Sat Nov 17 02:20:01 2007 +0000 @@ -1,6 +1,7 @@ /** * @file ft.h File Transfer API * @ingroup core + * @see @ref xfer-signals */ /* purple @@ -22,8 +23,6 @@ * You 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 - * - * @see @ref xfer-signals */ #ifndef _PURPLE_FT_H_ #define _PURPLE_FT_H_ @@ -243,6 +242,8 @@ * @param xfer The file transfer. * * @return The name of the remote user. + * + * @since 2.1.0 */ const char *purple_xfer_get_remote_user(const PurpleXfer *xfer);
--- a/libpurple/gaim-compat.h Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/gaim-compat.h Sat Nov 17 02:20:01 2007 +0000 @@ -22,8 +22,6 @@ * You 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 - * - * @see @ref account-signals */ #ifndef _GAIM_COMPAT_H_ #define _GAIM_COMPAT_H_
--- a/libpurple/imgstore.h Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/imgstore.h Sat Nov 17 02:20:01 2007 +0000 @@ -1,6 +1,7 @@ /** * @file imgstore.h IM Image Store API * @ingroup core + * @see @ref imgstore-signals */ /* purple @@ -22,8 +23,6 @@ * You 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 - * - * @see @ref imgstore-signals */ #ifndef _PURPLE_IMGSTORE_H_ #define _PURPLE_IMGSTORE_H_
--- a/libpurple/log.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/log.c Sat Nov 17 02:20:01 2007 +0000 @@ -756,7 +756,7 @@ if (!fwrite(image_data, image_byte_count, 1, image_file)) { purple_debug_error("log", "Error writing %s: %s\n", - path, strerror(errno)); + path, g_strerror(errno)); fclose(image_file); /* Attempt to not leave half-written files around. */ @@ -771,7 +771,7 @@ else { purple_debug_error("log", "Unable to create file %s: %s\n", - path, strerror(errno)); + path, g_strerror(errno)); } } @@ -1108,7 +1108,7 @@ return TRUE; else if (ret == -1) { - purple_debug_error("log", "Failed to delete: %s - %s\n", data->path, strerror(errno)); + purple_debug_error("log", "Failed to delete: %s - %s\n", data->path, g_strerror(errno)); } else { @@ -1143,7 +1143,7 @@ g_free(dirname); return TRUE; } - purple_debug_info("log", "access(%s) failed: %s\n", dirname, strerror(errno)); + purple_debug_info("log", "access(%s) failed: %s\n", dirname, g_strerror(errno)); g_free(dirname); #else /* Unless and until someone writes equivalent win32 code, @@ -1638,7 +1638,7 @@ if (!(index = g_fopen(pathstr, "rb"))) { purple_debug_error("log", "Failed to open index file \"%s\" for reading: %s\n", - pathstr, strerror(errno)); + pathstr, g_strerror(errno)); /* Fall through so that we'll parse the log file. */ } @@ -1675,7 +1675,7 @@ if (!(file = g_fopen(purple_stringref_value(pathref), "rb"))) { purple_debug_error("log", "Failed to open log file \"%s\" for reading: %s\n", - purple_stringref_value(pathref), strerror(errno)); + purple_stringref_value(pathref), g_strerror(errno)); purple_stringref_unref(pathref); g_free(pathstr); return NULL; @@ -1684,7 +1684,7 @@ index_tmp = g_strdup_printf("%s.XXXXXX", pathstr); if ((index_fd = g_mkstemp(index_tmp)) == -1) { purple_debug_error("log", "Failed to open index temp file: %s\n", - strerror(errno)); + g_strerror(errno)); g_free(pathstr); g_free(index_tmp); index = NULL; @@ -1692,7 +1692,7 @@ if ((index = fdopen(index_fd, "wb")) == NULL) { purple_debug_error("log", "Failed to fdopen() index temp file: %s\n", - strerror(errno)); + g_strerror(errno)); close(index_fd); if (index_tmp != NULL) { @@ -1828,7 +1828,7 @@ if (g_rename(index_tmp, pathstr)) { purple_debug_warning("log", "Failed to rename index temp file \"%s\" to \"%s\": %s\n", - index_tmp, pathstr, strerror(errno)); + index_tmp, pathstr, g_strerror(errno)); g_unlink(index_tmp); g_free(index_tmp); }
--- a/libpurple/log.h Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/log.h Sat Nov 17 02:20:01 2007 +0000 @@ -1,6 +1,7 @@ /** * @file log.h Logging API * @ingroup core + * @see @ref log-signals */ /* purple @@ -22,8 +23,6 @@ * You 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 - * - * @see @ref log-signals */ #ifndef _PURPLE_LOG_H_ #define _PURPLE_LOG_H_
--- a/libpurple/nat-pmp.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/nat-pmp.c Sat Nov 17 02:20:01 2007 +0000 @@ -312,7 +312,7 @@ if (sendto(sendfd, &req, sizeof(req), 0, (struct sockaddr *)(gateway), sizeof(struct sockaddr)) < 0) { - purple_debug_info("nat-pmp", "There was an error sending the NAT-PMP public IP request! (%s)\n", strerror(errno)); + purple_debug_info("nat-pmp", "There was an error sending the NAT-PMP public IP request! (%s)\n", g_strerror(errno)); g_free(gateway); pmp_info.status = PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER; return NULL; @@ -320,7 +320,7 @@ if (setsockopt(sendfd, SOL_SOCKET, SO_RCVTIMEO, &req_timeout, sizeof(req_timeout)) < 0) { - purple_debug_info("nat-pmp", "There was an error setting the socket's options! (%s)\n", strerror(errno)); + purple_debug_info("nat-pmp", "There was an error setting the socket's options! (%s)\n", g_strerror(errno)); g_free(gateway); pmp_info.status = PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER; return NULL; @@ -332,7 +332,7 @@ { if (errno != EAGAIN) { - purple_debug_info("nat-pmp", "There was an error receiving the response from the NAT-PMP device! (%s)\n", strerror(errno)); + purple_debug_info("nat-pmp", "There was an error receiving the response from the NAT-PMP device! (%s)\n", g_strerror(errno)); g_free(gateway); pmp_info.status = PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER; return NULL; @@ -432,13 +432,13 @@ /* TODO: Non-blocking! */ success = (sendto(sendfd, &req, sizeof(req), 0, (struct sockaddr *)(gateway), sizeof(struct sockaddr)) >= 0); if (!success) - purple_debug_info("nat-pmp", "There was an error sending the NAT-PMP mapping request! (%s)\n", strerror(errno)); + purple_debug_info("nat-pmp", "There was an error sending the NAT-PMP mapping request! (%s)\n", g_strerror(errno)); if (success) { success = (setsockopt(sendfd, SOL_SOCKET, SO_RCVTIMEO, &req_timeout, sizeof(req_timeout)) >= 0); if (!success) - purple_debug_info("nat-pmp", "There was an error setting the socket's options! (%s)\n", strerror(errno)); + purple_debug_info("nat-pmp", "There was an error setting the socket's options! (%s)\n", g_strerror(errno)); } if (success) @@ -448,7 +448,7 @@ success = ((recvfrom(sendfd, resp, sizeof(PurplePmpMapResponse), 0, NULL, NULL) >= 0) || (errno == EAGAIN)); if (!success) - purple_debug_info("nat-pmp", "There was an error receiving the response from the NAT-PMP device! (%s)\n", strerror(errno)); + purple_debug_info("nat-pmp", "There was an error receiving the response from the NAT-PMP device! (%s)\n", g_strerror(errno)); } if (success)
--- a/libpurple/network.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/network.c Sat Nov 17 02:20:01 2007 +0000 @@ -259,10 +259,17 @@ return FALSE; } +static gboolean listen_map_external = TRUE; +void purple_network_listen_map_external(gboolean map_external) +{ + listen_map_external = map_external; +} + static PurpleNetworkListenData * purple_network_do_listen(unsigned short port, int socket_type, PurpleNetworkListenCallback cb, gpointer cb_data) { int listenfd = -1; + int flags; const int on = 1; PurpleNetworkListenData *listen_data; unsigned short actual_port; @@ -284,7 +291,7 @@ #ifndef _WIN32 purple_debug_warning("network", "getaddrinfo: %s\n", gai_strerror(errnum)); if (errnum == EAI_SYSTEM) - purple_debug_warning("network", "getaddrinfo: system error: %s\n", strerror(errno)); + purple_debug_warning("network", "getaddrinfo: system error: %s\n", g_strerror(errno)); #else purple_debug_warning("network", "getaddrinfo: Error Code = %d\n", errnum); #endif @@ -301,7 +308,7 @@ if (listenfd < 0) continue; if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0) - purple_debug_warning("network", "setsockopt: %s\n", strerror(errno)); + purple_debug_warning("network", "setsockopt: %s\n", g_strerror(errno)); if (bind(listenfd, next->ai_addr, next->ai_addrlen) == 0) break; /* success */ /* XXX - It is unclear to me (datallah) whether we need to be @@ -317,30 +324,31 @@ struct sockaddr_in sockin; if ((listenfd = socket(AF_INET, socket_type, 0)) < 0) { - purple_debug_warning("network", "socket: %s\n", strerror(errno)); + purple_debug_warning("network", "socket: %s\n", g_strerror(errno)); return NULL; } if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0) - purple_debug_warning("network", "setsockopt: %s\n", strerror(errno)); + purple_debug_warning("network", "setsockopt: %s\n", g_strerror(errno)); memset(&sockin, 0, sizeof(struct sockaddr_in)); sockin.sin_family = PF_INET; sockin.sin_port = htons(port); if (bind(listenfd, (struct sockaddr *)&sockin, sizeof(struct sockaddr_in)) != 0) { - purple_debug_warning("network", "bind: %s\n", strerror(errno)); + purple_debug_warning("network", "bind: %s\n", g_strerror(errno)); close(listenfd); return NULL; } #endif if (socket_type == SOCK_STREAM && listen(listenfd, 4) != 0) { - purple_debug_warning("network", "listen: %s\n", strerror(errno)); + purple_debug_warning("network", "listen: %s\n", g_strerror(errno)); close(listenfd); return NULL; } - fcntl(listenfd, F_SETFL, O_NONBLOCK); + flags = fcntl(listenfd, F_GETFL); + fcntl(listenfd, F_SETFL, flags | O_NONBLOCK); actual_port = purple_network_get_port_from_fd(listenfd); @@ -354,11 +362,17 @@ listen_data->cb_data = cb_data; listen_data->socket_type = socket_type; + if (!listen_map_external) + { + purple_debug_info("network", "Skipping external port mapping.\n"); + /* The pmp_map_cb does what we want to do */ + purple_timeout_add(0, purple_network_finish_pmp_map_cb, listen_data); + } /* 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), + else 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\n",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); } @@ -424,7 +438,7 @@ len = sizeof(addr); if (getsockname(fd, (struct sockaddr *) &addr, &len) == -1) { - purple_debug_warning("network", "getsockname: %s\n", strerror(errno)); + purple_debug_warning("network", "getsockname: %s\n", g_strerror(errno)); return 0; } @@ -565,7 +579,7 @@ retval = WSALookupServiceEnd(h); - g_idle_add(wpurple_network_change_thread_cb, NULL); + purple_timeout_add(0, wpurple_network_change_thread_cb, NULL); }
--- a/libpurple/network.h Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/network.h Sat Nov 17 02:20:01 2007 +0000 @@ -106,6 +106,20 @@ */ const char *purple_network_get_my_ip(int fd); +#ifndef PURPLE_DISABLE_DEPRECATED +/** + * Should calls to purple_network_listen() and purple_network_listen_range() + * map the port externally using NAT-PMP or UPnP? + * The default value is TRUE + * + * @param map_external Should the open port be mapped externally? + * @deprecated In 3.0.0 a boolean will be added to the above functions to + * perform the same function. + * @since 2.3.0 + */ +void purple_network_listen_map_external(gboolean map_external); +#endif + /** * Attempts to open a listening port ONLY on the specified port number. * You probably want to use purple_network_listen_range() instead of this.
--- a/libpurple/notify.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/notify.c Sat Nov 17 02:20:01 2007 +0000 @@ -688,8 +688,11 @@ void purple_notify_user_info_remove_last_item(PurpleNotifyUserInfo *user_info) { - user_info->user_info_entries = g_list_remove(user_info->user_info_entries, - g_list_last(user_info->user_info_entries)->data); + GList *last = g_list_last(user_info->user_info_entries); + if (last) { + purple_notify_user_info_entry_destroy(last->data); + user_info->user_info_entries = g_list_delete_link(user_info->user_info_entries, last); + } } void *
--- a/libpurple/notify.h Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/notify.h Sat Nov 17 02:20:01 2007 +0000 @@ -1,6 +1,7 @@ /** * @file notify.h Notification API * @ingroup core + * @see @ref notify-signals */ /* purple @@ -22,8 +23,6 @@ * You 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 - * - * @see @ref notify-signals */ #ifndef _PURPLE_NOTIFY_H_ #define _PURPLE_NOTIFY_H_ @@ -290,7 +289,7 @@ */ void purple_notify_searchresults_row_add(PurpleNotifySearchResults *results, GList *row); - +#ifndef PURPLE_DISABLE_DEPRECATED /** * Returns a number of the rows in the search results object. * @@ -309,7 +308,9 @@ * @return Number of the result rows. */ guint purple_notify_searchresults_get_rows_count(PurpleNotifySearchResults *results); +#endif +#ifndef PURPLE_DISABLE_DEPRECATED /** * Returns a number of the columns in the search results object. * @@ -328,7 +329,9 @@ * @return Number of the columns. */ guint purple_notify_searchresults_get_columns_count(PurpleNotifySearchResults *results); +#endif +#ifndef PURPLE_DISABLE_DEPRECATED /** * Returns a row of the results from the search results object. * @@ -349,7 +352,9 @@ */ GList *purple_notify_searchresults_row_get(PurpleNotifySearchResults *results, unsigned int row_id); +#endif +#ifndef PURPLE_DISABLE_DEPRECATED /** * Returns a title of the search results object's column. * @@ -368,6 +373,7 @@ */ char *purple_notify_searchresults_column_get_title(PurpleNotifySearchResults *results, unsigned int column_id); +#endif /*@}*/ @@ -540,7 +546,7 @@ void purple_notify_user_info_prepend_pair(PurpleNotifyUserInfo *user_info, const char *label, const char *value); /** - * Remove a PurpleNotifyUserInfoEntry from a PurpleNotifyUserInfo object + * Remove a PurpleNotifyUserInfoEntry from a PurpleNotifyUserInfo object without freeing the entry. * * @param user_info The PurpleNotifyUserInfo * @param user_info_entry The PurpleNotifyUserInfoEntry
--- a/libpurple/plugin.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/plugin.c Sat Nov 17 02:20:01 2007 +0000 @@ -58,13 +58,9 @@ #ifdef PURPLE_PLUGINS static GList *load_queue = NULL; static GList *plugin_loaders = NULL; +static GList *plugins_to_disable = NULL; #endif -/* - * TODO: I think the intention was to allow multiple load and unload - * callback functions. Perhaps using a GList instead of a - * pointer to a single function. - */ static void (*probe_cb)(void *) = NULL; static void *probe_cb_data = NULL; static void (*load_cb)(PurplePlugin *, void *) = NULL; @@ -254,7 +250,6 @@ * plugins being added to the global name space. * * G_MODULE_BIND_LOCAL was added in glib 2.3.3. - * TODO: I guess there's nothing we can do about that? */ #if GLIB_CHECK_VERSION(2,3,3) plugin->handle = g_module_open(filename, G_MODULE_BIND_LOCAL); @@ -625,7 +620,6 @@ plugin->loaded = TRUE; - /* TODO */ if (load_cb != NULL) load_cb(plugin, load_cb_data); @@ -643,43 +637,37 @@ { #ifdef PURPLE_PLUGINS GList *l; + GList *ll; g_return_val_if_fail(plugin != NULL, FALSE); - - loaded_plugins = g_list_remove(loaded_plugins, plugin); - if ((plugin->info != NULL) && PURPLE_IS_PROTOCOL_PLUGIN(plugin)) - protocol_plugins = g_list_remove(protocol_plugins, plugin); - g_return_val_if_fail(purple_plugin_is_loaded(plugin), FALSE); purple_debug_info("plugins", "Unloading plugin %s\n", plugin->info->name); - /* cancel any pending dialogs the plugin has */ - purple_request_close_with_handle(plugin); - purple_notify_close_with_handle(plugin); - - plugin->loaded = FALSE; - /* Unload all plugins that depend on this plugin. */ - while ((l = plugin->dependent_plugins) != NULL) - { + for (l = plugin->dependent_plugins; l != NULL; l = ll) { const char * dep_name = (const char *)l->data; PurplePlugin *dep_plugin; + /* Store a pointer to the next element in the list. + * This is because we'll be modifying this list in the loop. */ + ll = l->next; + dep_plugin = purple_plugins_find_with_id(dep_name); if (dep_plugin != NULL && purple_plugin_is_loaded(dep_plugin)) { if (!purple_plugin_unload(dep_plugin)) { - char *tmp; - - tmp = g_strdup_printf(_("The dependent plugin %s failed to unload."), - _(dep_plugin->info->name)); - - purple_notify_error(NULL, NULL, - _("There were errors unloading the plugin."), tmp); - g_free(tmp); + g_free(plugin->error); + plugin->error = g_strdup_printf(_("%s requires %s, but it failed to unload."), + _(plugin->info->name), + _(dep_plugin->info->name)); + return FALSE; + } + else + { + plugin->dependent_plugins = g_list_delete_link(plugin->dependent_plugins, l); } } } @@ -699,8 +687,8 @@ } if (plugin->native_plugin) { - if (plugin->info->unload != NULL) - plugin->info->unload(plugin); + if (plugin->info->unload && !plugin->info->unload(plugin)) + return FALSE; if (plugin->info->type == PURPLE_PLUGIN_PROTOCOL) { PurplePluginProtocolInfo *prpl_info; @@ -724,8 +712,7 @@ prpl_info->protocol_options = NULL; } } - } - else { + } else { PurplePlugin *loader; PurplePluginLoaderInfo *loader_info; @@ -736,14 +723,30 @@ loader_info = PURPLE_PLUGIN_LOADER_INFO(loader); - if (loader_info->unload != NULL) - loader_info->unload(plugin); + if (loader_info->unload && !loader_info->unload(plugin)) + return FALSE; } + /* cancel any pending dialogs the plugin has */ + purple_request_close_with_handle(plugin); + purple_notify_close_with_handle(plugin); + purple_signals_disconnect_by_handle(plugin); purple_plugin_ipc_unregister_all(plugin); - /* TODO */ + loaded_plugins = g_list_remove(loaded_plugins, plugin); + if ((plugin->info != NULL) && PURPLE_IS_PROTOCOL_PLUGIN(plugin)) + protocol_plugins = g_list_remove(protocol_plugins, plugin); + plugins_to_disable = g_list_remove(plugins_to_disable, plugin); + plugin->loaded = FALSE; + + /* We wouldn't be anywhere near here if the plugin wasn't loaded, so + * if plugin->error is set at all, it had to be from a previous + * unload failure. It's obviously okay now. + */ + g_free(plugin->error); + plugin->error = NULL; + if (unload_cb != NULL) unload_cb(plugin, unload_cb_data); @@ -757,6 +760,17 @@ #endif /* PURPLE_PLUGINS */ } +void +purple_plugin_disable(PurplePlugin *plugin) +{ +#ifdef PURPLE_PLUGINS + g_return_if_fail(plugin != NULL); + + if (!g_list_find(plugins_to_disable, plugin)) + plugins_to_disable = g_list_prepend(plugins_to_disable, plugin); +#endif +} + gboolean purple_plugin_reload(PurplePlugin *plugin) { @@ -1222,14 +1236,14 @@ #ifdef PURPLE_PLUGINS GList *pl; GList *files = NULL; - PurplePlugin *p; for (pl = purple_plugins_get_loaded(); pl != NULL; pl = pl->next) { - p = pl->data; + PurplePlugin *plugin = pl->data; - if (p->info->type != PURPLE_PLUGIN_PROTOCOL && - p->info->type != PURPLE_PLUGIN_LOADER) { - files = g_list_append(files, p->path); + if (plugin->info->type != PURPLE_PLUGIN_PROTOCOL && + plugin->info->type != PURPLE_PLUGIN_LOADER && + !g_list_find(plugins_to_disable, plugin)) { + files = g_list_append(files, plugin->path); } } @@ -1391,6 +1405,7 @@ if (probe_cb != NULL) probe_cb(probe_cb_data); + #endif /* PURPLE_PLUGINS */ } @@ -1464,7 +1479,6 @@ void purple_plugins_register_probe_notify_cb(void (*func)(void *), void *data) { - /* TODO */ probe_cb = func; probe_cb_data = data; } @@ -1472,7 +1486,6 @@ void purple_plugins_unregister_probe_notify_cb(void (*func)(void *)) { - /* TODO */ probe_cb = NULL; probe_cb_data = NULL; } @@ -1481,7 +1494,6 @@ purple_plugins_register_load_notify_cb(void (*func)(PurplePlugin *, void *), void *data) { - /* TODO */ load_cb = func; load_cb_data = data; } @@ -1489,7 +1501,6 @@ void purple_plugins_unregister_load_notify_cb(void (*func)(PurplePlugin *, void *)) { - /* TODO */ load_cb = NULL; load_cb_data = NULL; } @@ -1498,7 +1509,6 @@ purple_plugins_register_unload_notify_cb(void (*func)(PurplePlugin *, void *), void *data) { - /* TODO */ unload_cb = func; unload_cb_data = data; } @@ -1506,7 +1516,6 @@ void purple_plugins_unregister_unload_notify_cb(void (*func)(PurplePlugin *, void *)) { - /* TODO */ unload_cb = NULL; unload_cb_data = NULL; }
--- a/libpurple/plugin.h Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/plugin.h Sat Nov 17 02:20:01 2007 +0000 @@ -1,6 +1,9 @@ /** * @file plugin.h Plugin API * @ingroup core + * @see @ref plugin-signals + * @see @ref plugin-ids + * @see @ref plugin-i18n */ /* purple @@ -22,10 +25,6 @@ * You 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 - * - * @see @ref plugin-signals - * @see @ref plugin-ids - * @see @ref plugin-i18n */ #ifndef _PURPLE_PLUGIN_H_ #define _PURPLE_PLUGIN_H_ @@ -71,11 +70,6 @@ * * This is used in the version 2.0 API and up. */ -/* TODO We need to figure out exactly what parts of this are required. The - * dependent plugin unloading stuff was causing crashes with perl and tcl - * plugins because they didn't set ids and the dependency code was requiring - * them. Then we need to actually make sure that plugins have all the right - * parts before loading them. */ struct _PurplePluginInfo { unsigned int magic; @@ -297,6 +291,18 @@ gboolean purple_plugin_unload(PurplePlugin *plugin); /** + * Disable a plugin. + * + * This function adds the plugin to a list of plugins to "disable at the next + * startup" by excluding said plugins from the list of plugins to save. The + * UI needs to call purple_plugins_save_loaded() after calling this for it + * to have any effect. + * + * @since 2.3.0 + */ +void purple_plugin_disable(PurplePlugin *plugin); + +/** * Reloads a plugin. * * @param plugin The old plugin handle. @@ -526,53 +532,71 @@ */ gboolean purple_plugins_enabled(void); +#ifndef PURPLE_DISABLE_DEPRECATED /** * Registers a function that will be called when probing is finished. * * @param func The callback function. * @param data Data to pass to the callback. + * @deprecated If you need this, ask for a plugin-probe signal to be added. */ void purple_plugins_register_probe_notify_cb(void (*func)(void *), void *data); +#endif +#ifndef PURPLE_DISABLE_DEPRECATED /** * Unregisters a function that would be called when probing is finished. * * @param func The callback function. + * @deprecated If you need this, ask for a plugin-probe signal to be added. */ void purple_plugins_unregister_probe_notify_cb(void (*func)(void *)); +#endif +#ifndef PURPLE_DISABLE_DEPRECATED /** * Registers a function that will be called when a plugin is loaded. * * @param func The callback function. * @param data Data to pass to the callback. + * @deprecated Use the plugin-load signal instead. */ void purple_plugins_register_load_notify_cb(void (*func)(PurplePlugin *, void *), void *data); +#endif +#ifndef PURPLE_DISABLE_DEPRECATED /** * Unregisters a function that would be called when a plugin is loaded. * * @param func The callback function. + * @deprecated Use the plugin-load signal instead. */ void purple_plugins_unregister_load_notify_cb(void (*func)(PurplePlugin *, void *)); +#endif +#ifndef PURPLE_DISABLE_DEPRECATED /** * Registers a function that will be called when a plugin is unloaded. * * @param func The callback function. * @param data Data to pass to the callback. + * @deprecated Use the plugin-unload signal instead. */ void purple_plugins_register_unload_notify_cb(void (*func)(PurplePlugin *, void *), void *data); +#endif +#ifndef PURPLE_DISABLE_DEPRECATED /** * Unregisters a function that would be called when a plugin is unloaded. * * @param func The callback function. + * @deprecated Use the plugin-unload signal instead. */ void purple_plugins_unregister_unload_notify_cb(void (*func)(PurplePlugin *, void *)); +#endif /** * Finds a plugin with the specified name. @@ -613,7 +637,7 @@ /** * Returns a list of all loaded plugins. * - * @return A list of all loaded plugins. + * @constreturn A list of all loaded plugins. */ GList *purple_plugins_get_loaded(void); @@ -623,14 +647,14 @@ * to the PURPLE_INIT_PLUGIN() macro, or if it was compiled * against an incompatable API version. * - * @return A list of all protocol plugins. + * @constreturn A list of all protocol plugins. */ GList *purple_plugins_get_protocols(void); /** * Returns a list of all plugins, whether loaded or not. * - * @return A list of all plugins. + * @constreturn A list of all plugins. */ GList *purple_plugins_get_all(void);
--- a/libpurple/pluginpref.h Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/pluginpref.h Sat Nov 17 02:20:01 2007 +0000 @@ -35,16 +35,16 @@ */ typedef enum { - PURPLE_STRING_FORMAT_TYPE_NONE = 0, - PURPLE_STRING_FORMAT_TYPE_MULTILINE = 1 << 0, - PURPLE_STRING_FORMAT_TYPE_HTML = 1 << 1 + PURPLE_STRING_FORMAT_TYPE_NONE = 0, /**< The string is plain text. */ + PURPLE_STRING_FORMAT_TYPE_MULTILINE = 1 << 0, /**< The string can have newlines. */ + PURPLE_STRING_FORMAT_TYPE_HTML = 1 << 1 /**< The string can be in HTML. */ } PurpleStringFormatType; typedef enum { PURPLE_PLUGIN_PREF_NONE, PURPLE_PLUGIN_PREF_CHOICE, - PURPLE_PLUGIN_PREF_INFO, /**< no-value label */ - PURPLE_PLUGIN_PREF_STRING_FORMAT + PURPLE_PLUGIN_PREF_INFO, /**< no-value label */ + PURPLE_PLUGIN_PREF_STRING_FORMAT /**< The preference has a string value. */ } PurplePluginPrefType; #include <glib.h> @@ -85,7 +85,7 @@ * Get the plugin preferences from a plugin preference frame * * @param frame The plugin frame to get the plugin preferences from - * @return a GList of plugin preferences + * @constreturn a GList of plugin preferences */ GList *purple_plugin_pref_frame_get_prefs(PurplePluginPrefFrame *frame); @@ -207,7 +207,7 @@ * Get the choices for a choices plugin pref * * @param pref The plugin pref - * @return GList of the choices + * @constreturn GList of the choices */ GList *purple_plugin_pref_get_choices(PurplePluginPref *pref);
--- a/libpurple/plugins/autoaccept.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/plugins/autoaccept.c Sat Nov 17 02:20:01 2007 +0000 @@ -252,7 +252,7 @@ PLUGIN_ID, /* plugin id */ PLUGIN_NAME, /* name */ - VERSION, /* version */ + DISPLAY_VERSION, /* version */ PLUGIN_SUMMARY, /* summary */ PLUGIN_DESCRIPTION, /* description */ PLUGIN_AUTHOR, /* author */
--- a/libpurple/plugins/buddynote.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/plugins/buddynote.c Sat Nov 17 02:20:01 2007 +0000 @@ -88,7 +88,7 @@ PURPLE_PRIORITY_DEFAULT, /**< priority */ "core-plugin_pack-buddynote", /**< id */ N_("Buddy Notes"), /**< name */ - VERSION, /**< version */ + DISPLAY_VERSION, /**< version */ N_("Store notes on particular buddies."), /**< summary */ N_("Adds the option to store notes for buddies " "on your buddy list."), /**< description */
--- a/libpurple/plugins/ciphertest.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/plugins/ciphertest.c Sat Nov 17 02:20:01 2007 +0000 @@ -262,7 +262,7 @@ "core-cipher-test", /**< id */ N_("Cipher Test"), /**< name */ - VERSION, /**< version */ + DISPLAY_VERSION, /**< version */ /** summary */ N_("Tests the ciphers that ship with libpurple."), /** description */
--- a/libpurple/plugins/dbus-example.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/plugins/dbus-example.c Sat Nov 17 02:20:01 2007 +0000 @@ -153,7 +153,7 @@ "dbus-example", /**< id */ N_("DBus Example"), /**< name */ - VERSION, /**< version */ + DISPLAY_VERSION, /**< version */ /** summary */ N_("DBus Plugin Example"), /** description */
--- a/libpurple/plugins/debug_example.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/plugins/debug_example.c Sat Nov 17 02:20:01 2007 +0000 @@ -109,7 +109,7 @@ PLUGIN_ID, /* id */ "Debug API Example", /* name */ - VERSION, /* version */ + DISPLAY_VERSION, /* version */ "Debug API Example", /* summary */ "Debug API Example", /* description */ PLUGIN_AUTHOR, /* author */
--- a/libpurple/plugins/filectl.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/plugins/filectl.c Sat Nov 17 02:20:01 2007 +0000 @@ -246,7 +246,7 @@ FILECTL_PLUGIN_ID, /**< id */ N_("File Control"), /**< name */ - VERSION, /**< version */ + DISPLAY_VERSION, /**< version */ /** summary */ N_("Allows control by entering commands in a file."), /** description */
--- a/libpurple/plugins/helloworld.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/plugins/helloworld.c Sat Nov 17 02:20:01 2007 +0000 @@ -114,8 +114,8 @@ "core-hello_world", "Hello World!", - VERSION, /* This constant is defined in version.h, but you shouldn't use it for - your own plugins. We use it here because it's our plugin. */ + DISPLAY_VERSION, /* This constant is defined in config.h, but you shouldn't use it for + your own plugins. We use it here because it's our plugin. And we're lazy. */ "Hello World Plugin", "Hello World Plugin",
--- a/libpurple/plugins/idle.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/plugins/idle.c Sat Nov 17 02:20:01 2007 +0000 @@ -316,7 +316,7 @@ /* This is a cultural reference. Dy'er Mak'er is a song by Led Zeppelin. If that doesn't translate well into your language, drop the 's before translating. */ N_("I'dle Mak'er"), - VERSION, + DISPLAY_VERSION, N_("Allows you to hand-configure how long you've been idle"), N_("Allows you to hand-configure how long you've been idle"), "Eric Warmenhoven <eric@warmenhoven.org>",
--- a/libpurple/plugins/ipc-test-client.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/plugins/ipc-test-client.c Sat Nov 17 02:20:01 2007 +0000 @@ -85,7 +85,7 @@ IPC_TEST_CLIENT_PLUGIN_ID, /**< id */ N_("IPC Test Client"), /**< name */ - VERSION, /**< version */ + DISPLAY_VERSION, /**< version */ /** summary */ N_("Test plugin IPC support, as a client."), /** description */
--- a/libpurple/plugins/ipc-test-server.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/plugins/ipc-test-server.c Sat Nov 17 02:20:01 2007 +0000 @@ -72,7 +72,7 @@ IPC_TEST_SERVER_PLUGIN_ID, /**< id */ N_("IPC Test Server"), /**< name */ - VERSION, /**< version */ + DISPLAY_VERSION, /**< version */ /** summary */ N_("Test plugin IPC support, as a server."), /** description */
--- a/libpurple/plugins/joinpart.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/plugins/joinpart.c Sat Nov 17 02:20:01 2007 +0000 @@ -268,7 +268,7 @@ JOINPART_PLUGIN_ID, /**< id */ N_("Join/Part Hiding"), /**< name */ - VERSION, /**< version */ + DISPLAY_VERSION, /**< version */ /** summary */ N_("Hides extraneous join/part messages."), /** description */
--- a/libpurple/plugins/log_reader.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/plugins/log_reader.c Sat Nov 17 02:20:01 2007 +0000 @@ -28,6 +28,19 @@ NAME_GUESS_THEM }; +/* Some common functions. */ +static int get_month(const char *month) +{ + int iter; + const char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL}; + for (iter = 0; months[iter]; iter++) { + if (strcmp(month, months[iter]) == 0) + break; + } + return iter; +} + /***************************************************************************** * Adium Logger * @@ -103,9 +116,10 @@ } else { char *filename = g_build_filename(path, file, NULL); FILE *handle = g_fopen(filename, "rb"); - char *contents; + char contents[57]; /* XXX: This is really inflexible. */ char *contents2; struct adium_logger_data *data; + size_t rd; PurpleLog *log; if (!handle) { @@ -113,11 +127,9 @@ continue; } - /* XXX: This is really inflexible. */ - contents = g_malloc(57); - fread(contents, 56, 1, handle); + rd = fread(contents, 1, 56, handle) == 0; fclose(handle); - contents[56] = '\0'; + contents[rd] = '\0'; /* XXX: This is fairly inflexible. */ contents2 = contents; @@ -135,11 +147,9 @@ purple_debug_error("Adium log parse", "Contents timestamp parsing error\n"); - g_free(contents); g_free(filename); continue; } - g_free(contents); data = g_new0(struct adium_logger_data, 1); data->path = filename; @@ -168,21 +178,20 @@ } else { char *filename = g_build_filename(path, file, NULL); FILE *handle = g_fopen(filename, "rb"); - char *contents; + char contents[14]; /* XXX: This is really inflexible. */ char *contents2; struct adium_logger_data *data; PurpleLog *log; + size_t rd; if (!handle) { g_free(filename); continue; } - /* XXX: This is really inflexible. */ - contents = g_malloc(14); - fread(contents, 13, 1, handle); + rd = fread(contents, 1, 13, handle); fclose(handle); - contents[13] = '\0'; + contents[rd] = '\0'; contents2 = contents; while (*contents2 && *contents2 != '(') @@ -195,13 +204,10 @@ purple_debug_error("Adium log parse", "Contents timestamp parsing error\n"); - g_free(contents); g_free(filename); continue; } - g_free(contents); - tm.tm_year -= 1900; tm.tm_mon -= 1; @@ -232,7 +238,6 @@ struct adium_logger_data *data; GError *error = NULL; gchar *read = NULL; - gsize length; /* XXX: TODO: We probably want to set PURPLE_LOG_READ_NO_NEWLINE * XXX: TODO: for HTML logs. */ @@ -246,8 +251,9 @@ g_return_val_if_fail(data->path != NULL, g_strdup("")); purple_debug_info("Adium log read", "Reading %s\n", data->path); - if (!g_file_get_contents(data->path, &read, &length, &error)) { - purple_debug_error("Adium log read", "Error reading log\n"); + if (!g_file_get_contents(data->path, &read, NULL, &error)) { + purple_debug_error("Adium log read", "Error reading log: %s\n", + (error && error->message) ? error->message : "Unknown error"); if (error) g_error_free(error); return g_strdup(""); @@ -1355,36 +1361,7 @@ * daylight savings time. */ tm.tm_isdst = -1; - - /* Ugly hack, in case current locale - * is not English. This code is taken - * from log.c. - */ - if (strcmp(month, "Jan") == 0) { - tm.tm_mon= 0; - } else if (strcmp(month, "Feb") == 0) { - tm.tm_mon = 1; - } else if (strcmp(month, "Mar") == 0) { - tm.tm_mon = 2; - } else if (strcmp(month, "Apr") == 0) { - tm.tm_mon = 3; - } else if (strcmp(month, "May") == 0) { - tm.tm_mon = 4; - } else if (strcmp(month, "Jun") == 0) { - tm.tm_mon = 5; - } else if (strcmp(month, "Jul") == 0) { - tm.tm_mon = 6; - } else if (strcmp(month, "Aug") == 0) { - tm.tm_mon = 7; - } else if (strcmp(month, "Sep") == 0) { - tm.tm_mon = 8; - } else if (strcmp(month, "Oct") == 0) { - tm.tm_mon = 9; - } else if (strcmp(month, "Nov") == 0) { - tm.tm_mon = 10; - } else if (strcmp(month, "Dec") == 0) { - tm.tm_mon = 11; - } + tm.tm_mon = get_month(month); data = g_new0( struct trillian_logger_data, 1); @@ -1446,7 +1423,7 @@ file = g_fopen(data->path, "rb"); fseek(file, data->offset, SEEK_SET); - fread(read, data->length, 1, file); + data->length = fread(read, 1, data->length, file); fclose(file); if (read[data->length-1] == '\n') { @@ -1808,8 +1785,10 @@ error = NULL; if (!g_file_get_contents(path, &contents, NULL, &error)) { purple_debug_error("QIP logger", - "Couldn't read file %s: %s \n", path, error->message); - g_error_free(error); + "Couldn't read file %s: %s \n", path, + (error && error->message) ? error->message : "Unknown error"); + if (error) + g_error_free(error); g_free(path); return list; } @@ -1937,15 +1916,13 @@ g_return_val_if_fail(data->path != NULL, g_strdup("")); g_return_val_if_fail(data->length > 0, g_strdup("")); - error = NULL; - - contents = g_malloc(data->length + 2); - file = g_fopen(data->path, "rb"); g_return_val_if_fail(file != NULL, g_strdup("")); - + + contents = g_malloc(data->length + 2); + fseek(file, data->offset, SEEK_SET); - fread(contents, data->length, 1, file); + data->length = fread(contents, 1, data->length, file); fclose(file); contents[data->length] = '\n'; @@ -1955,8 +1932,10 @@ error = NULL; if (!(utf8_string = g_convert(contents, -1, "UTF-8", "Cp1251", NULL, NULL, &error))) { purple_debug_error("QIP logger", - "Couldn't convert file %s to UTF-8: %s\n", data->path, error->message); - g_error_free(error); + "Couldn't convert file %s to UTF-8: %s\n", data->path, + (error && error->message) ? error->message : "Unknown error"); + if (error) + g_error_free(error); g_free(contents); return g_strdup(""); } @@ -2022,7 +2001,7 @@ g_string_append(formatted, "</font> "); if (is_in_message) { - if (buddy_name != NULL && buddy->alias) { + if (buddy_name != NULL && buddy != NULL && buddy->alias) { g_string_append_printf(formatted, "<span style=\"color: #A82F2F;\">" "<b>%s</b></span>: ", buddy->alias); @@ -2052,7 +2031,9 @@ g_string_append(formatted, line); g_string_append(formatted, "<br>"); } - line = ++c; + + if (c) + line = ++c; } } g_free(contents); @@ -2094,6 +2075,347 @@ g_free(data); } +/************************************************************************* + * aMSN Logger * + *************************************************************************/ + +/* The aMSN logger doesn't write logs, only reads them. This is to include + * aMSN logs in the log viewer transparently. + */ + +static PurpleLogLogger *amsn_logger; + +struct amsn_logger_data { + char *path; + int offset; + int length; +}; + +#define AMSN_LOG_CONV_START "|\"LRED[Conversation started on " +#define AMSN_LOG_CONV_END "|\"LRED[You have closed the window on " +#define AMSN_LOG_CONV_EXTRA "01 Aug 2001 00:00:00]" + +static GList *amsn_logger_parse_file(char *filename, const char *sn, PurpleAccount *account) +{ + GList *list = NULL; + GError *error; + char *contents; + struct amsn_logger_data *data; + PurpleLog *log; + + purple_debug_info("aMSN logger", "Reading %s\n", filename); + error = NULL; + if (!g_file_get_contents(filename, &contents, NULL, &error)) { + purple_debug_error("aMSN logger", + "Couldn't read file %s: %s \n", filename, + (error && error->message) ? + error->message : "Unknown error"); + if (error) + g_error_free(error); + } else { + char *c = contents; + gboolean found_start = FALSE; + char *start_log = c; + int offset = 0; + struct tm tm; + while (c && *c) { + if (purple_str_has_prefix(c, AMSN_LOG_CONV_START)) { + char month[4]; + if (sscanf(c + strlen(AMSN_LOG_CONV_START), + "%u %3s %u %u:%u:%u", + &tm.tm_mday, (char*)&month, &tm.tm_year, + &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) { + found_start = FALSE; + purple_debug_error("aMSN logger", + "Error parsing start date for %s\n", + filename); + } else { + tm.tm_year -= 1900; + + /* Let the C library deal with + * daylight savings time. + */ + tm.tm_isdst = -1; + tm.tm_mon = get_month(month); + + found_start = TRUE; + offset = c - contents; + start_log = c; + } + } else if (purple_str_has_prefix(c, AMSN_LOG_CONV_END) && found_start) { + data = g_new0(struct amsn_logger_data, 1); + data->path = g_strdup(filename); + data->offset = offset; + data->length = c - start_log + + strlen(AMSN_LOG_CONV_END) + + strlen(AMSN_LOG_CONV_EXTRA); + log = purple_log_new(PURPLE_LOG_IM, sn, account, NULL, mktime(&tm), NULL); + log->logger = amsn_logger; + log->logger_data = data; + list = g_list_prepend(list, log); + found_start = FALSE; + + purple_debug_info("aMSN logger", + "Found log for %s:" + " path = (%s)," + " offset = (%d)," + " length = (%d)\n", + sn, data->path, data->offset, data->length); + } + c = strstr(c, "\n"); + c++; + } + + /* I've seen the file end without the AMSN_LOG_CONV_END bit */ + if (found_start) { + data = g_new0(struct amsn_logger_data, 1); + data->path = g_strdup(filename); + data->offset = offset; + data->length = c - start_log + + strlen(AMSN_LOG_CONV_END) + + strlen(AMSN_LOG_CONV_EXTRA); + log = purple_log_new(PURPLE_LOG_IM, sn, account, NULL, mktime(&tm), NULL); + log->logger = amsn_logger; + log->logger_data = data; + list = g_list_prepend(list, log); + found_start = FALSE; + + purple_debug_info("aMSN logger", + "Found log for %s:" + " path = (%s)," + " offset = (%d)," + " length = (%d)\n", + sn, data->path, data->offset, data->length); + } + g_free(contents); + } + + return list; +} + +/* `log_dir`/username@hotmail.com/logs/buddyname@hotmail.com.log */ +/* `log_dir`/username@hotmail.com/logs/Month Year/buddyname@hotmail.com.log */ +static GList *amsn_logger_list(PurpleLogType type, const char *sn, PurpleAccount *account) +{ + GList *list = NULL; + const char *logdir; + char *username; + char *log_path; + char *buddy_log; + char *filename; + GDir *dir; + const char *name; + + logdir = purple_prefs_get_string("/plugins/core/log_reader/amsn/log_directory"); + + /* By clearing the log directory path, this logger can be (effectively) disabled. */ + if (!logdir || !*logdir) + return NULL; + + /* aMSN only works with MSN/WLM */ + if (strcmp(account->protocol_id, "prpl-msn")) + return NULL; + + username = g_strdup(purple_normalize(account, account->username)); + buddy_log = g_strdup_printf("%s.log", purple_normalize(account, sn)); + log_path = g_build_filename(logdir, username, "logs", NULL); + + /* First check in the top-level */ + filename = g_build_filename(log_path, buddy_log, NULL); + if (g_file_test(filename, G_FILE_TEST_EXISTS)) + list = amsn_logger_parse_file(filename, sn, account); + else + g_free(filename); + + /* Check in previous months */ + dir = g_dir_open(log_path, 0, NULL); + if (dir) { + while ((name = g_dir_read_name(dir)) != NULL) { + filename = g_build_filename(log_path, name, buddy_log, NULL); + if (g_file_test(filename, G_FILE_TEST_EXISTS)) + list = g_list_concat(list, amsn_logger_parse_file(filename, sn, account)); + g_free(filename); + } + g_dir_close(dir); + } + + g_free(log_path); + + /* New versions use 'friendlier' directory names */ + purple_util_chrreplace(username, '@', '_'); + purple_util_chrreplace(username, '.', '_'); + + log_path = g_build_filename(logdir, username, "logs", NULL); + + /* First check in the top-level */ + filename = g_build_filename(log_path, buddy_log, NULL); + if (g_file_test(filename, G_FILE_TEST_EXISTS)) + list = g_list_concat(list, amsn_logger_parse_file(filename, sn, account)); + g_free(filename); + + /* Check in previous months */ + dir = g_dir_open(log_path, 0, NULL); + if (dir) { + while ((name = g_dir_read_name(dir)) != NULL) { + filename = g_build_filename(log_path, name, buddy_log, NULL); + if (g_file_test(filename, G_FILE_TEST_EXISTS)) + list = g_list_concat(list, amsn_logger_parse_file(filename, sn, account)); + g_free(filename); + } + g_dir_close(dir); + } + + g_free(log_path); + g_free(username); + g_free(buddy_log); + + return list; +} + +/* Really it's |"L, but the string's been escaped */ +#define AMSN_LOG_FORMAT_TAG "|"L" + +static char *amsn_logger_read(PurpleLog *log, PurpleLogReadFlags *flags) +{ + struct amsn_logger_data *data; + FILE *file; + char *contents; + char *escaped; + GString *formatted; + char *start; + gboolean in_span = FALSE; + + if (flags != NULL) + *flags = PURPLE_LOG_READ_NO_NEWLINE; + + g_return_val_if_fail(log != NULL, g_strdup("")); + + data = log->logger_data; + + g_return_val_if_fail(data->path != NULL, g_strdup("")); + g_return_val_if_fail(data->length > 0, g_strdup("")); + + contents = g_malloc(data->length + 2); + + file = g_fopen(data->path, "rb"); + g_return_val_if_fail(file != NULL, g_strdup("")); + + fseek(file, data->offset, SEEK_SET); + data->length = fread(contents, 1, data->length, file); + fclose(file); + + contents[data->length] = '\n'; + contents[data->length + 1] = '\0'; + + escaped = g_markup_escape_text(contents, -1); + g_free(contents); + contents = escaped; + + formatted = g_string_sized_new(data->length + 2); + + start = contents; + while (start && *start) { + char *end; + char *old_tag; + char *tag; + end = strstr(start, "\n"); + if (!end) + break; + *end = '\0'; + if (purple_str_has_prefix(start, AMSN_LOG_FORMAT_TAG) && in_span) { + /* New format for this line */ + g_string_append(formatted, "</span><br>"); + in_span = FALSE; + } else if (start != contents) { + /* Continue format from previous line */ + g_string_append(formatted, "<br>"); + } + old_tag = start; + tag = strstr(start, AMSN_LOG_FORMAT_TAG); + while (tag) { + g_string_append_len(formatted, old_tag, tag - old_tag); + tag += strlen(AMSN_LOG_FORMAT_TAG); + if (in_span) { + g_string_append(formatted, "</span>"); + in_span = FALSE; + } + if (*tag == 'C') { + /* |"LCxxxxxx is a hex colour */ + char colour[7]; + strncpy(colour, tag + 1, 6); + colour[6] = '\0'; + g_string_append_printf(formatted, "<span style=\"color: #%s;\">", colour); + /* This doesn't appear to work? */ + /* g_string_append_printf(formatted, "<span style=\"color: #%6s;\">", tag + 1); */ + in_span = TRUE; + old_tag = tag + 7; /* C + xxxxxx */ + } else { + /* |"Lxxx is a 3-digit colour code */ + if (purple_str_has_prefix(tag, "RED")) { + g_string_append(formatted, "<span style=\"color: red;\">"); + in_span = TRUE; + } else if (purple_str_has_prefix(tag, "GRA")) { + g_string_append(formatted, "<span style=\"color: gray;\">"); + in_span = TRUE; + } else if (purple_str_has_prefix(tag, "NOR")) { + g_string_append(formatted, "<span style=\"color: black;\">"); + in_span = TRUE; + } else if (purple_str_has_prefix(tag, "ITA")) { + g_string_append(formatted, "<span style=\"color: blue;\">"); + in_span = TRUE; + } else if (purple_str_has_prefix(tag, "GRE")) { + g_string_append(formatted, "<span style=\"color: darkgreen;\">"); + in_span = TRUE; + } else { + purple_debug_info("aMSN logger", "Unknown colour format: %3s\n", tag); + } + old_tag = tag + 3; + } + tag = strstr(tag, AMSN_LOG_FORMAT_TAG); + } + g_string_append(formatted, old_tag); + start = end + 1; + } + if (in_span) + g_string_append(formatted, "</span>"); + + g_free(contents); + + return g_string_free(formatted, FALSE); +} + +static int amsn_logger_size(PurpleLog *log) +{ + struct amsn_logger_data *data; + char *text; + int size; + + g_return_val_if_fail(log != NULL, 0); + + data = log->logger_data; + + if (purple_prefs_get_bool("/plugins/core/log_reader/fast_sizes")) { + return data ? data->length : 0; + } + + text = amsn_logger_read(log, NULL); + size = strlen(text); + g_free(text); + + return size; +} + +static void amsn_logger_finalize(PurpleLog *log) +{ + struct amsn_logger_data *data; + + g_return_if_fail(log != NULL); + + data = log->logger_data; + g_free(data->path); + g_free(data); +} + /***************************************************************************** * Plugin Code * *****************************************************************************/ @@ -2101,14 +2423,16 @@ static void init_plugin(PurplePlugin *plugin) { + +} + +static void log_reader_init_prefs() { char *path; #ifdef _WIN32 char *folder; gboolean found = FALSE; #endif - g_return_if_fail(plugin != NULL); - purple_prefs_add_none("/plugins/core/log_reader"); @@ -2151,18 +2475,18 @@ /* Calculate default Messenger Plus! log directory. */ #ifdef _WIN32 + path = NULL; folder = wpurple_get_special_folder(CSIDL_PERSONAL); if (folder) { path = g_build_filename(folder, "My Chat Logs", NULL); g_free(folder); - } else - path = g_strdup(""); + } #else path = g_build_filename(PURPLE_LOG_READER_WINDOWS_MOUNT_POINT, "Documents and Settings", g_get_user_name(), "My Documents", "My Chat Logs", NULL); #endif - purple_prefs_add_string("/plugins/core/log_reader/messenger_plus/log_directory", path); + purple_prefs_add_string("/plugins/core/log_reader/messenger_plus/log_directory", path ? path : ""); g_free(path); @@ -2171,18 +2495,18 @@ /* Calculate default MSN message history directory. */ #ifdef _WIN32 + path = NULL; folder = wpurple_get_special_folder(CSIDL_PERSONAL); if (folder) { path = g_build_filename(folder, "My Received Files", NULL); g_free(folder); - } else - path = g_strdup(""); + } #else path = g_build_filename(PURPLE_LOG_READER_WINDOWS_MOUNT_POINT, "Documents and Settings", g_get_user_name(), "My Documents", "My Received Files", NULL); #endif - purple_prefs_add_string("/plugins/core/log_reader/msn/log_directory", path); + purple_prefs_add_string("/plugins/core/log_reader/msn/log_directory", path ? path : ""); g_free(path); @@ -2261,21 +2585,22 @@ g_key_file_free(key_file); } #else /* !GLIB_CHECK_VERSION(2,6,0) */ - gsize length; gchar *contents = NULL; purple_debug_info("Trillian talk.ini read", - "Reading %s\n", path); - if (!g_file_get_contents(path, &contents, &length, &error)) { + "Reading %s\n", path); + if (!g_file_get_contents(path, &contents, NULL, &error)) { purple_debug_error("Trillian talk.ini read", - "Error reading talk.ini\n"); + "Error reading talk.ini: %s\n", + (error && error->message) ? error->message : "Unknown error"); if (error) g_error_free(error); } else { - char *line = contents; - while (*contents) { - if (*contents == '\n') { - *contents = '\0'; + char *cursor, *line; + line = cursor = contents; + while (*cursor) { + if (*cursor == '\n') { + *cursor = '\0'; /* XXX: This assumes the first Directory key is under [Logging]. */ if (purple_str_has_prefix(line, "Directory=")) { @@ -2287,25 +2612,29 @@ found = TRUE; } - contents++; - line = contents; + cursor++; + line = cursor; } else - contents++; + cursor++; } - g_free(path); g_free(contents); } + g_free(path); #endif /* !GTK_CHECK_VERSION(2,6,0) */ } /* path */ if (!found) { + path = NULL; folder = wpurple_get_special_folder(CSIDL_PROGRAM_FILES); if (folder) { path = g_build_filename(folder, "Trillian", "users", "default", "logs", NULL); g_free(folder); - } else - path = g_strdup(""); + } + + purple_prefs_add_string( + "/plugins/core/log_reader/trillian/log_directory", path ? path : ""); + g_free(path); } #else /* !defined(_WIN32) */ /* TODO: At some point, this could attempt to parse talk.ini @@ -2316,25 +2645,44 @@ path = g_build_filename(PURPLE_LOG_READER_WINDOWS_MOUNT_POINT, "Program Files", "Trillian", "users", "default", "logs", NULL); + purple_prefs_add_string( + "/plugins/core/log_reader/trillian/log_directory", path); + g_free(path); #endif - /* Add QIP log directory preference. */ purple_prefs_add_none("/plugins/core/log_reader/qip"); /* Calculate default QIP log directory. */ #ifdef _WIN32 + path = NULL; folder = wpurple_get_special_folder(CSIDL_PROGRAM_FILES); if (folder) { path = g_build_filename(folder, "QIP", "Users", NULL); g_free(folder); - } else - path = g_strdup(""); + } #else path = g_build_filename(PURPLE_LOG_READER_WINDOWS_MOUNT_POINT, "Program Files", "QIP", "Users", NULL); #endif - purple_prefs_add_string("/plugins/core/log_reader/qip/log_directory", path); + purple_prefs_add_string("/plugins/core/log_reader/qip/log_directory", path ? path : ""); + g_free(path); + + /* Add aMSN Messenger log directory preference. */ + purple_prefs_add_none("/plugins/core/log_reader/amsn"); + + /* Calculate default aMSN log directory. */ +#ifdef _WIN32 + path = NULL; + folder = wpurple_get_special_folder(CSIDL_PROFILE); /* Silly aMSN, not using CSIDL_APPDATA */ + if (folder) { + path = g_build_filename(folder, "amsn", NULL); + g_free(folder); + } +#else + path = g_build_filename(purple_home_dir(), ".amsn", NULL); +#endif + purple_prefs_add_string("/plugins/core/log_reader/amsn/log_directory", path ? path : ""); g_free(path); } @@ -2343,6 +2691,8 @@ { g_return_val_if_fail(plugin != NULL, FALSE); + log_reader_init_prefs(); + /* The names of IM clients are marked for translation at the request of translators who wanted to transliterate them. Many translators choose to leave them alone. Choose what's best for your language. */ @@ -2418,6 +2768,18 @@ trillian_logger_size); purple_log_logger_add(trillian_logger); + /* The names of IM clients are marked for translation at the request of + translators who wanted to transliterate them. Many translators + choose to leave them alone. Choose what's best for your language. */ + amsn_logger = purple_log_logger_new("amsn", _("aMSN"), 6, + NULL, + NULL, + amsn_logger_finalize, + amsn_logger_list, + amsn_logger_read, + amsn_logger_size); + purple_log_logger_add(amsn_logger); + return TRUE; } @@ -2434,6 +2796,7 @@ purple_log_logger_remove(msn_logger); purple_log_logger_remove(trillian_logger); purple_log_logger_remove(qip_logger); + purple_log_logger_remove(amsn_logger); return TRUE; } @@ -2494,6 +2857,10 @@ "/plugins/core/log_reader/trillian/log_directory", _("Trillian")); purple_plugin_pref_frame_add(frame, ppref); + ppref = purple_plugin_pref_new_with_name_and_label( + "/plugins/core/log_reader/amsn/log_directory", _("aMSN")); + purple_plugin_pref_frame_add(frame, ppref); + return frame; } @@ -2521,7 +2888,7 @@ PURPLE_PRIORITY_DEFAULT, /**< priority */ "core-log_reader", /**< id */ N_("Log Reader"), /**< name */ - VERSION, /**< version */ + DISPLAY_VERSION, /**< version */ /** summary */ N_("Includes other IM clients' logs in the "
--- a/libpurple/plugins/mono/loader/mono.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/plugins/mono/loader/mono.c Sat Nov 17 02:20:01 2007 +0000 @@ -215,7 +215,7 @@ PURPLE_PRIORITY_DEFAULT, MONO_PLUGIN_ID, N_("Mono Plugin Loader"), - VERSION, + DISPLAY_VERSION, N_("Loads .NET plugins with Mono."), N_("Loads .NET plugins with Mono."), "Eoin Coffey <ecoffey@simla.colostate.edu>",
--- a/libpurple/plugins/newline.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/plugins/newline.c Sat Nov 17 02:20:01 2007 +0000 @@ -66,7 +66,7 @@ "core-plugin_pack-newline", /**< id */ N_("New Line"), /**< name */ - VERSION, /**< version */ + DISPLAY_VERSION, /**< version */ N_("Prepends a newline to displayed message."), /**< summary */ N_("Prepends a newline to messages so that the " "rest of the message appears below the "
--- a/libpurple/plugins/notify_example.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/plugins/notify_example.c Sat Nov 17 02:20:01 2007 +0000 @@ -135,7 +135,7 @@ PLUGIN_ID, /* id */ "Notify API Example", /* name */ - VERSION, /* version */ + DISPLAY_VERSION, /* version */ "Notify API Example", /* summary */ "Notify API Example", /* description */ PLUGIN_AUTHOR, /* author */
--- a/libpurple/plugins/offlinemsg.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/plugins/offlinemsg.c Sat Nov 17 02:20:01 2007 +0000 @@ -225,7 +225,7 @@ PLUGIN_ID, /* plugin id */ PLUGIN_NAME, /* name */ - VERSION, /* version */ + DISPLAY_VERSION, /* version */ PLUGIN_SUMMARY, /* summary */ PLUGIN_DESCRIPTION, /* description */ PLUGIN_AUTHOR, /* author */
--- a/libpurple/plugins/perl/common/PluginPref.xs Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/plugins/perl/common/PluginPref.xs Sat Nov 17 02:20:01 2007 +0000 @@ -93,6 +93,10 @@ purple_plugin_pref_get_masked(pref) Purple::PluginPref pref +Purple::String::Format::Type +purple_plugin_pref_get_format_type(pref) + Purple::PluginPref pref + unsigned int purple_plugin_pref_get_max_length(pref) Purple::PluginPref pref @@ -145,6 +149,11 @@ gboolean mask void +purple_plugin_pref_set_format_type(pref, format) + Purple::PluginPref pref + Purple::String::Format::Type format + +void purple_plugin_pref_set_max_length(pref, max_length) Purple::PluginPref pref unsigned int max_length
--- a/libpurple/plugins/perl/common/module.h Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/plugins/perl/common/module.h Sat Nov 17 02:20:01 2007 +0000 @@ -197,6 +197,7 @@ typedef PurplePluginPref * Purple__PluginPref; typedef PurplePluginPrefFrame * Purple__PluginPref__Frame; typedef PurplePluginPrefType Purple__PluginPrefType; +typedef PurpleStringFormatType Purple__String__Format__Type; /* pounce.h */ typedef PurplePounce * Purple__Pounce;
--- a/libpurple/plugins/perl/common/typemap Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/plugins/perl/common/typemap Sat Nov 17 02:20:01 2007 +0000 @@ -158,6 +158,7 @@ const Purple::StatusType T_PurpleObj Purple::StoredImage T_PurpleObj +Purple::String::Format::Type T_IV Purple::Stringref T_PurpleObj Purple::Util::FetchUrlData T_PTR Purple::Util::InfoFieldFormatCallback T_PTR
--- a/libpurple/plugins/perl/perl.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/plugins/perl/perl.c Sat Nov 17 02:20:01 2007 +0000 @@ -599,7 +599,7 @@ PERL_PLUGIN_ID, /**< id */ N_("Perl Plugin Loader"), /**< name */ - VERSION, /**< version */ + DISPLAY_VERSION, /**< version */ N_("Provides support for loading perl plugins."), /**< summary */ N_("Provides support for loading perl plugins."), /**< description */ "Christian Hammond <chipx86@gnupdate.org>", /**< author */
--- a/libpurple/plugins/pluginpref_example.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/plugins/pluginpref_example.c Sat Nov 17 02:20:01 2007 +0000 @@ -131,7 +131,7 @@ "core-pluginpref_example", /**< id */ "Pluginpref Example", /**< name */ - VERSION, /**< version */ + DISPLAY_VERSION, /**< version */ /** summary */ "An example of how to use pluginprefs", /** description */
--- a/libpurple/plugins/psychic.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/plugins/psychic.c Sat Nov 17 02:20:01 2007 +0000 @@ -147,7 +147,7 @@ PLUGIN_ID, /**< id */ PLUGIN_NAME, /**< name */ - VERSION, /**< version */ + DISPLAY_VERSION, /**< version */ PLUGIN_SUMMARY, /**< summary */ PLUGIN_DESC, /**< description */ PLUGIN_AUTHOR, /**< author */
--- a/libpurple/plugins/signals-test.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/plugins/signals-test.c Sat Nov 17 02:20:01 2007 +0000 @@ -226,6 +226,18 @@ purple_account_get_username(purple_connection_get_account(gc))); } +static void +connection_error_cb(PurpleConnection *gc, + PurpleConnectionError err, + const gchar *desc, + void *data) +{ + const gchar *username = + purple_account_get_username(purple_connection_get_account(gc)); + purple_debug_misc("signals test", "connection-error (%s, %u, %s)\n", + username, err, desc); +} + /************************************************************************** * Conversation subsystem signal callbacks **************************************************************************/ @@ -626,6 +638,8 @@ plugin, PURPLE_CALLBACK(signing_off_cb), NULL); purple_signal_connect(conn_handle, "signed-off", plugin, PURPLE_CALLBACK(signed_off_cb), NULL); + purple_signal_connect(conn_handle, "connection-error", + plugin, PURPLE_CALLBACK(connection_error_cb), NULL); /* Conversations subsystem signals */ purple_signal_connect(conv_handle, "writing-im-msg", @@ -737,7 +751,7 @@ SIGNAL_TEST_PLUGIN_ID, /**< id */ N_("Signals Test"), /**< name */ - VERSION, /**< version */ + DISPLAY_VERSION, /**< version */ /** summary */ N_("Test to see that all signals are working properly."), /** description */
--- a/libpurple/plugins/simple.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/plugins/simple.c Sat Nov 17 02:20:01 2007 +0000 @@ -35,7 +35,7 @@ SIMPLE_PLUGIN_ID, /**< id */ N_("Simple Plugin"), /**< name */ - VERSION, /**< version */ + DISPLAY_VERSION, /**< version */ /** summary */ N_("Tests to see that most things are working."), /** description */
--- a/libpurple/plugins/ssl/ssl-gnutls.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/plugins/ssl/ssl-gnutls.c Sat Nov 17 02:20:01 2007 +0000 @@ -884,7 +884,7 @@ gnutls_x509_crt crt_dat; /* GnuTLS time functions return this on error */ const time_t errval = (time_t) (-1); - + gboolean success = TRUE; g_return_val_if_fail(crt, FALSE); g_return_val_if_fail(crt->scheme == &x509_gnutls, FALSE); @@ -893,16 +893,16 @@ if (activation) { *activation = gnutls_x509_crt_get_activation_time(crt_dat); + if (*activation == errval) + success = FALSE; } if (expiration) { *expiration = gnutls_x509_crt_get_expiration_time(crt_dat); + if (*expiration == errval) + success = FALSE; } - if (*activation == errval || *expiration == errval) { - return FALSE; - } - - return TRUE; + return success; } /* X.509 certificate operations provided by this plugin */ @@ -993,7 +993,7 @@ SSL_GNUTLS_PLUGIN_ID, /**< id */ N_("GNUTLS"), /**< name */ - VERSION, /**< version */ + DISPLAY_VERSION, /**< version */ /** summary */ N_("Provides SSL support through GNUTLS."), /** description */
--- a/libpurple/plugins/ssl/ssl-nss.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/plugins/ssl/ssl-nss.c Sat Nov 17 02:20:01 2007 +0000 @@ -386,6 +386,7 @@ static GList * ssl_nss_peer_certs(PurpleSslConnection *gsc) { +#if 0 PurpleSslNssData *nss_data = PURPLE_SSL_NSS_DATA(gsc); CERTCertificate *cert; /* @@ -397,6 +398,10 @@ /* TODO: this is a blind guess */ cert = SSL_PeerCertificate(nss_data->fd); + if (cert) + CERT_DestroyCertificate(cert); +#endif + return NULL; @@ -430,11 +435,12 @@ filename); /* Load the raw data up */ - g_return_val_if_fail( - g_file_get_contents(filename, - &rawcert, &len, - NULL ), - NULL); + if (!g_file_get_contents(filename, + &rawcert, &len, + NULL)) { + purple_debug_error("nss/x509", "Unable to read certificate file.\n"); + return NULL; + } /* Decode the certificate */ crt_dat = CERT_DecodeCertFromPackage(rawcert, len); @@ -808,7 +814,7 @@ SSL_NSS_PLUGIN_ID, /**< id */ N_("NSS"), /**< name */ - VERSION, /**< version */ + DISPLAY_VERSION, /**< version */ /** summary */ N_("Provides SSL support through Mozilla NSS."), /** description */
--- a/libpurple/plugins/ssl/ssl.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/plugins/ssl/ssl.c Sat Nov 17 02:20:01 2007 +0000 @@ -92,7 +92,7 @@ SSL_PLUGIN_ID, /**< id */ N_("SSL"), /**< name */ - VERSION, /**< version */ + DISPLAY_VERSION, /**< version */ /** summary */ N_("Provides a wrapper around SSL support libraries."), /** description */
--- a/libpurple/plugins/statenotify.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/plugins/statenotify.c Sat Nov 17 02:20:01 2007 +0000 @@ -146,7 +146,7 @@ STATENOTIFY_PLUGIN_ID, /**< id */ N_("Buddy State Notification"), /**< name */ - VERSION, /**< version */ + DISPLAY_VERSION, /**< version */ /** summary */ N_("Notifies in a conversation window when a buddy goes or returns from " "away or idle."),
--- a/libpurple/plugins/tcl/tcl.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/plugins/tcl/tcl.c Sat Nov 17 02:20:01 2007 +0000 @@ -198,7 +198,7 @@ } if (ferror(fp)) { - purple_debug(PURPLE_DEBUG_ERROR, "tcl", "error reading %s (%s)\n", plugin->path, strerror(errno)); + purple_debug(PURPLE_DEBUG_ERROR, "tcl", "error reading %s (%s)\n", plugin->path, g_strerror(errno)); g_free(buf); fclose(fp); return FALSE; @@ -421,7 +421,7 @@ PURPLE_PRIORITY_DEFAULT, "core-tcl", N_("Tcl Plugin Loader"), - VERSION, + DISPLAY_VERSION, N_("Provides support for loading Tcl plugins"), N_("Provides support for loading Tcl plugins"), "Ethan Blanton <eblanton@cs.purdue.edu>",
--- a/libpurple/pounce.h Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/pounce.h Sat Nov 17 02:20:01 2007 +0000 @@ -339,7 +339,7 @@ /** * Returns a list of all registered buddy pounces. * - * @return The list of buddy pounces. + * @constreturn The list of buddy pounces. */ GList *purple_pounces_get_all(void);
--- a/libpurple/prefs.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/prefs.c Sat Nov 17 02:20:01 2007 +0000 @@ -297,6 +297,7 @@ g_filename_from_utf8(pref_value, -1, NULL, NULL, NULL)); } } + g_string_free(pref_name_full, TRUE); } else { char *decoded; @@ -382,13 +383,20 @@ purple_debug_info("prefs", "Reading %s\n", filename); if(!g_file_get_contents(filename, &contents, &length, &error)) { -#ifndef _WIN32 +#ifdef _WIN32 + gchar *common_appdata = wpurple_get_special_folder(CSIDL_COMMON_APPDATA); +#endif g_free(filename); g_error_free(error); error = NULL; +#ifdef _WIN32 + filename = g_build_filename(common_appdata ? common_appdata : "", "purple", "prefs.xml", NULL); + g_free(common_appdata); +#else filename = g_build_filename(SYSCONFDIR, "purple", "prefs.xml", NULL); +#endif purple_debug_info("prefs", "Reading %s\n", filename); @@ -401,15 +409,6 @@ return FALSE; } -#else /* _WIN32 */ - purple_debug_error("prefs", "Error reading prefs: %s\n", - error->message); - g_error_free(error); - g_free(filename); - prefs_loaded = TRUE; - - return FALSE; -#endif /* _WIN32 */ } context = g_markup_parse_context_new(&prefs_parser, 0, NULL, NULL);
--- a/libpurple/prefs.h Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/prefs.h Sat Nov 17 02:20:01 2007 +0000 @@ -45,9 +45,20 @@ } PurplePrefType; /** - * Pref change callback type + * The type of callbacks for preference changes. + * + * @param name the name of the preference which has changed. + * @param type the type of the preferenced named @a name + * @param val the new value of the preferencs; should be cast to the correct + * type. For instance, to recover the value of a #PURPLE_PREF_INT + * preference, use <tt>GPOINTER_TO_INT(val)</tt>. Alternatively, + * just call purple_prefs_get_int(), purple_prefs_get_string_list() + * etc. + * @param data Arbitrary data specified when the callback was connected with + * purple_prefs_connect_callback(). + * + * @see purple_prefs_connect_callback() */ - typedef void (*PurplePrefCallback) (const char *name, PurplePrefType type, gconstpointer val, gpointer data); @@ -115,6 +126,9 @@ * * @param name The name of the pref * @param value The initial value to set + * @note This function takes a copy of the strings in the value list. The list + * itself and original copies of the strings are up to the caller to + * free. */ void purple_prefs_add_string_list(const char *name, GList *value); @@ -131,6 +145,9 @@ * * @param name The name of the pref * @param value The initial value to set + * @note This function takes a copy of the strings in the value list. The list + * itself and original copies of the strings are up to the caller to + * free. */ void purple_prefs_add_path_list(const char *name, GList *value); @@ -291,6 +308,8 @@ * @return A list of newly allocated strings denoting the names of the children. * Returns @c NULL if there are no children or if pref doesn't exist. * The caller must free all the strings and the list. + * + * @since 2.1.0 */ GList *purple_prefs_get_children_names(const char *name);
--- a/libpurple/protocols/Makefile.am Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/Makefile.am Sat Nov 17 02:20:01 2007 +0000 @@ -1,5 +1,5 @@ EXTRA_DIST = Makefile.mingw -DIST_SUBDIRS = bonjour gg irc jabber msn myspace novell null oscar qq sametime silc silc10 toc simple yahoo zephyr +DIST_SUBDIRS = bonjour gg irc jabber msn msnp9 myspace novell null oscar qq sametime silc silc10 toc simple yahoo zephyr SUBDIRS = $(DYNAMIC_PRPLS) $(STATIC_PRPLS)
--- a/libpurple/protocols/Makefile.mingw Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/Makefile.mingw Sat Nov 17 02:20:01 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 silc simple yahoo bonjour myspace +SUBDIRS = gg irc jabber msnp9 novell null oscar qq sametime silc simple yahoo bonjour myspace .PHONY: all install clean
--- a/libpurple/protocols/bonjour/Makefile.am Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/bonjour/Makefile.am Sat Nov 17 02:20:01 2007 +0000 @@ -1,5 +1,6 @@ EXTRA_DIST = \ mdns_win32.c \ + dns_sd_proxy.c \ dns_sd_proxy.h \ Makefile.mingw @@ -17,7 +18,9 @@ mdns_interface.h \ mdns_types.h \ parser.c \ - parser.h + parser.h \ + bonjour_ft.c \ + bonjour_ft.h if MDNS_AVAHI BONJOURSOURCES += mdns_avahi.c
--- a/libpurple/protocols/bonjour/Makefile.mingw Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/bonjour/Makefile.mingw Sat Nov 17 02:20:01 2007 +0000 @@ -29,14 +29,13 @@ -I$(GTK_TOP)/include \ -I$(GTK_TOP)/include/glib-2.0 \ -I$(GTK_TOP)/lib/glib-2.0/include \ - -I$(BONJOUR_TOP)/include \ + -I$(BONJOUR_TOP)/Include \ -I$(LIBXML2_TOP)/include \ -I$(PURPLE_TOP) \ -I$(PURPLE_TOP)/win32 \ -I$(PIDGIN_TREE_TOP) LIB_PATHS += -L$(GTK_TOP)/lib \ - -L$(BONJOUR_TOP)/lib \ -L$(LIBXML2_TOP)/lib \ -L$(PURPLE_TOP) @@ -44,11 +43,13 @@ ## SOURCES, OBJECTS ## C_SRC = bonjour.c \ + bonjour_ft.c \ buddy.c \ + dns_sd_proxy.c \ + jabber.c \ mdns_common.c \ mdns_win32.c \ - parser.c \ - jabber.c + parser.c OBJECTS = $(C_SRC:%.c=%.o) @@ -59,11 +60,16 @@ -lglib-2.0 \ -lws2_32 \ -lintl \ - -ldnssd \ -lnetapi32 \ -lxml2 \ -lpurple +ifeq ($(LINK_DNS_SD_DIRECTLY), 1) + CFLAGS += -DLINK_DNS_SD_DIRECTLY + LIB_PATHS += -L$(BONJOUR_TOP)/lib/win32 -L$(BONJOUR_TOP)/lib + LIBS += -ldnssd +endif + include $(PIDGIN_COMMON_RULES) ##
--- a/libpurple/protocols/bonjour/bonjour.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/bonjour/bonjour.c Sat Nov 17 02:20:01 2007 +0000 @@ -26,6 +26,7 @@ #define UNICODE #include <windows.h> #include <lm.h> +#include "dns_sd_proxy.h" #endif #include "internal.h" @@ -40,6 +41,7 @@ #include "mdns_common.h" #include "jabber.h" #include "buddy.h" +#include "bonjour_ft.h" /* * TODO: Should implement an add_buddy callback that removes the buddy @@ -84,6 +86,7 @@ if (buddy->account != account) continue; purple_prpl_got_user_status(account, buddy->name, "offline", NULL); + purple_account_remove_buddy(account, buddy, NULL); purple_blist_remove_buddy(buddy); } } @@ -99,6 +102,17 @@ PurpleStatus *status; PurplePresence *presence; +#ifdef _WIN32 + if (!dns_sd_available()) { + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_OTHER_ERROR, + _("The Apple Bonjour For Windows toolkit wasn't found, see the FAQ at: " + "http://developer.pidgin.im/wiki/Using%20Pidgin#CanIusePidginforBonjourLink-LocalMessaging" + " for more information.")); + return; + } +#endif + gc->flags |= PURPLE_CONNECTION_HTML; gc->proto_data = bd = g_new0(BonjourData, 1); @@ -109,7 +123,9 @@ if (bonjour_jabber_start(bd->jabber_data) == -1) { /* Send a message about the connection error */ - purple_connection_error(gc, _("Unable to listen for incoming IM connections\n")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Unable to listen for incoming IM connections\n")); return; } @@ -134,7 +150,9 @@ bd->dns_sd_data->account = account; if (!bonjour_dns_sd_start(bd->dns_sd_data)) { - purple_connection_error(gc, _("Unable to establish connection with the local mDNS server. Is it running?")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Unable to establish connection with the local mDNS server. Is it running?")); return; } @@ -154,28 +172,35 @@ PurpleGroup *bonjour_group; BonjourData *bd = connection->proto_data; + /* Remove all the bonjour buddies */ + bonjour_removeallfromlocal(connection); + /* Stop looking for buddies in the LAN */ - if (bd->dns_sd_data != NULL) + if (bd != NULL && bd->dns_sd_data != NULL) { bonjour_dns_sd_stop(bd->dns_sd_data); bonjour_dns_sd_free(bd->dns_sd_data); } - if (bd->jabber_data != NULL) + if (bd != NULL && bd->jabber_data != NULL) { /* Stop waiting for conversations */ bonjour_jabber_stop(bd->jabber_data); g_free(bd->jabber_data); } - /* Remove all the bonjour buddies */ - bonjour_removeallfromlocal(connection); - /* Delete the bonjour group */ bonjour_group = purple_find_group(BONJOUR_GROUP_NAME); if (bonjour_group != NULL) purple_blist_remove_group(bonjour_group); + /* Cancel any file transfers */ + while (bd != NULL && bd->xfer_lists) { + purple_xfer_cancel_local(bd->xfer_lists->data); + } + + g_free(bd); + connection->proto_data = NULL; } static const char * @@ -435,8 +460,8 @@ NULL, /* roomlist_cancel */ NULL, /* roomlist_expand_category */ NULL, /* can_receive_file */ - NULL, /* send_file */ - NULL, /* new_xfer */ + bonjour_send_file, /* send_file */ + bonjour_new_xfer, /* new_xfer */ NULL, /* offline_message */ NULL, /* whiteboard_prpl_ops */ NULL, /* send_raw */ @@ -462,7 +487,7 @@ "prpl-bonjour", /**< id */ "Bonjour", /**< name */ - VERSION, /**< version */ + DISPLAY_VERSION, /**< version */ /** summary */ N_("Bonjour Protocol Plugin"), /** description */ @@ -569,7 +594,7 @@ fullname = g_utf16_to_utf8(username, -1, NULL, NULL, NULL); } - g_idle_add(_set_default_name_cb, fullname); + purple_timeout_add(0, _set_default_name_cb, fullname); return NULL; } @@ -634,7 +659,7 @@ /* TODO: Avoid 'localhost,' if possible */ if (gethostname(hostname, 255) != 0) { purple_debug_warning("bonjour", "Error when getting host name: %s. Using \"localhost.\"\n", - strerror(errno)); + g_strerror(errno)); strcpy(hostname, "localhost"); } default_hostname = g_strdup(hostname);
--- a/libpurple/protocols/bonjour/bonjour.h Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/bonjour/bonjour.h Sat Nov 17 02:20:01 2007 +0000 @@ -44,6 +44,7 @@ { BonjourDnsSd *dns_sd_data; BonjourJabber *jabber_data; + GList *xfer_lists; } BonjourData; #endif /* _BONJOUR_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/bonjour/bonjour_ft.c Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,891 @@ +/* + * purple - Bonjour Protocol Plugin + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "internal.h" +#include "util.h" +#include "debug.h" +#include "notify.h" +#include "proxy.h" +#include "ft.h" +#include "buddy.h" +#include "bonjour.h" +#include "bonjour_ft.h" +#include "cipher.h" + +static void +bonjour_bytestreams_init(PurpleXfer *xfer); +static void +bonjour_bytestreams_connect(PurpleXfer *xfer, PurpleBuddy *pb); +static void +bonjour_xfer_init(PurpleXfer *xfer); +static void +bonjour_xfer_receive(PurpleConnection *pc, const char *id, const char *sid, const char *from, + const int filesize, const char *filename, int option); +static void bonjour_free_xfer(PurpleXfer *xfer); + +/* Look for specific xfer handle */ +static unsigned int next_id = 0; + +static void +xep_ft_si_reject(BonjourData *bd, const char *id, const char *to, const char *error_code, const char *error_type) +{ + xmlnode *error_node = NULL; + xmlnode *tmp_node = NULL; + XepIq *iq = NULL; + + g_return_if_fail(error_code != NULL); + g_return_if_fail(error_type != NULL); + + if(!to || !id) + return; + + purple_debug_info("bonjour", "xep file transfer stream initialization error.\n"); + iq = xep_iq_new(bd, XEP_IQ_ERROR, to, purple_account_get_username(bd->jabber_data->account), id); + if(iq == NULL) + return; + + error_node = xmlnode_new_child(iq->node, "error"); + xmlnode_set_attrib(error_node, "code", error_code); + xmlnode_set_attrib(error_node, "type", error_type); + + /* TODO: Make this better */ + if (!strcmp(error_code, "403")) { + tmp_node = xmlnode_new_child(error_node, "forbidden"); + xmlnode_set_namespace(tmp_node, "urn:ietf:params:xml:ns:xmpp-stanzas"); + + tmp_node = xmlnode_new_child(error_node, "text"); + xmlnode_set_namespace(tmp_node, "urn:ietf:params:xml:ns:xmpp-stanzas"); + xmlnode_insert_data(tmp_node, "Offer Declined", -1); + } else if (!strcmp(error_code, "404")) { + tmp_node = xmlnode_new_child(error_node, "item-not-found"); + xmlnode_set_namespace(tmp_node, "urn:ietf:params:xml:ns:xmpp-stanzas"); + } + + xep_iq_send_and_free(iq); +} + +static void bonjour_xfer_cancel_send(PurpleXfer *xfer) +{ + purple_debug_info("bonjour", "Bonjour-xfer-cancel-send.\n"); + bonjour_free_xfer(xfer); +} + +static void bonjour_xfer_request_denied(PurpleXfer *xfer) +{ + XepXfer *xf = NULL; + + purple_debug_info("bonjour", "Bonjour-xfer-request-denied.\n"); + + xf = xfer->data; + if(xf) + xep_ft_si_reject(xf->data, xf->sid, xfer->who, "403", "cancel"); + + bonjour_free_xfer(xfer); +} + +static void bonjour_xfer_cancel_recv(PurpleXfer *xfer) +{ + purple_debug_info("bonjour", "Bonjour-xfer-cancel-recv.\n"); + bonjour_free_xfer(xfer); +} + +struct socket_cleanup { + int fd; + guint handle; +}; + +static void +_wait_for_socket_close(gpointer data, gint source, PurpleInputCondition cond) +{ + struct socket_cleanup *sc = data; + char buf[1]; + int ret; + + ret = recv(source, buf, 1, 0); + + if (ret == 0 || (ret == -1 && !(errno == EAGAIN || errno == EWOULDBLOCK))) { + purple_debug_info("bonjour", "Client completed recieving; closing server socket.\n"); + purple_input_remove(sc->handle); + close(sc->fd); + g_free(sc); + } +} + +static void bonjour_xfer_end(PurpleXfer *xfer) +{ + purple_debug_info("bonjour", "Bonjour-xfer-end.\n"); + + /* We can't allow the server side to close the connection until the client is complete, + * otherwise there is a RST resulting in an error on the client side */ + if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND && purple_xfer_is_completed(xfer)) { + struct socket_cleanup *sc = g_new0(struct socket_cleanup, 1); + sc->fd = xfer->fd; + xfer->fd = -1; + sc->handle = purple_input_add(sc->fd, PURPLE_INPUT_READ, + _wait_for_socket_close, sc); + } + + bonjour_free_xfer(xfer); +} + +static PurpleXfer* +bonjour_si_xfer_find(BonjourData *bd, const char *sid, const char *from) +{ + GList *xfers = NULL; + PurpleXfer *xfer = NULL; + XepXfer *xf = NULL; + + if(!sid || !from || !bd) + return NULL; + + purple_debug_info("bonjour", "Look for sid=%s from=%s xferlists.\n", + sid, from); + + for(xfers = bd->xfer_lists; xfers; xfers = xfers->next) { + xfer = xfers->data; + if(xfer == NULL) + break; + xf = xfer->data; + if(xf == NULL) + break; + if(xf->sid && xfer->who && !strcmp(xf->sid, sid) && + !strcmp(xfer->who, from)) + return xfer; + } + + purple_debug_info("bonjour", "Look for xfer list fail\n"); + + return NULL; +} + +static void +xep_ft_si_offer(PurpleXfer *xfer, const gchar *to) +{ + xmlnode *si_node = NULL; + xmlnode *feature = NULL; + xmlnode *field = NULL; + xmlnode *option = NULL; + xmlnode *value = NULL; + xmlnode *file = NULL; + xmlnode *x = NULL; + XepIq *iq = NULL; + XepXfer *xf = NULL; + BonjourData *bd = NULL; + char buf[32]; + + xf = xfer->data; + if(!xf) + return; + + bd = xf->data; + if(!bd) + return; + + purple_debug_info("bonjour", "xep file transfer stream initialization offer-id=%d.\n", next_id); + + /* Assign stream id. */ + g_free(xf->iq_id); + xf->iq_id = g_strdup_printf("%u", next_id++); + iq = xep_iq_new(xf->data, XEP_IQ_SET, to, purple_account_get_username(bd->jabber_data->account), xf->iq_id); + if(iq == NULL) + return; + + /*Construct Stream initialization offer message.*/ + si_node = xmlnode_new_child(iq->node, "si"); + xmlnode_set_namespace(si_node, "http://jabber.org/protocol/si"); + xmlnode_set_attrib(si_node, "profile", "http://jabber.org/protocol/si/profile/file-transfer"); + g_free(xf->sid); + xf->sid = g_strdup(xf->iq_id); + xmlnode_set_attrib(si_node, "id", xf->sid); + + file = xmlnode_new_child(si_node, "file"); + xmlnode_set_namespace(file, "http://jabber.org/protocol/si/profile/file-transfer"); + xmlnode_set_attrib(file, "name", xfer->filename); + g_snprintf(buf, sizeof(buf), "%" G_GSIZE_FORMAT, xfer->size); + xmlnode_set_attrib(file, "size", buf); + + feature = xmlnode_new_child(si_node, "feature"); + xmlnode_set_namespace(feature, "http://jabber.org/protocol/feature-neg"); + + x = xmlnode_new_child(feature, "x"); + xmlnode_set_namespace(x, "jabber:x:data"); + xmlnode_set_attrib(x, "type", "form"); + + field = xmlnode_new_child(x, "field"); + xmlnode_set_attrib(field, "var", "stream-method"); + xmlnode_set_attrib(field, "type", "list-single"); + + if (xf->mode & XEP_BYTESTREAMS) { + option = xmlnode_new_child(field, "option"); + value = xmlnode_new_child(option, "value"); + xmlnode_insert_data(value, "http://jabber.org/protocol/bytestreams", -1); + } + if (xf->mode & XEP_IBB) { + option = xmlnode_new_child(field, "option"); + value = xmlnode_new_child(option, "value"); + xmlnode_insert_data(value, "http://jabber.org/protocol/ibb", -1); + } + + xep_iq_send_and_free(iq); +} + +static void +xep_ft_si_result(PurpleXfer *xfer, char *to) +{ + xmlnode *si_node = NULL; + xmlnode *feature = NULL; + xmlnode *field = NULL; + xmlnode *value = NULL; + xmlnode *x = NULL; + XepIq *iq = NULL; + XepXfer *xf = NULL; + BonjourData *bd; + + if(!to || !xfer) + return; + xf = xfer->data; + if(!xf) + return; + + bd = xf->data; + + purple_debug_info("bonjour", "xep file transfer stream initialization result.\n"); + iq = xep_iq_new(bd, XEP_IQ_RESULT, to, purple_account_get_username(bd->jabber_data->account), xf->iq_id); + if(iq == NULL) + return; + + si_node = xmlnode_new_child(iq->node, "si"); + xmlnode_set_namespace(si_node, "http://jabber.org/protocol/si"); + /*xmlnode_set_attrib(si_node, "profile", "http://jabber.org/protocol/si/profile/file-transfer");*/ + + feature = xmlnode_new_child(si_node, "feature"); + xmlnode_set_namespace(feature, "http://jabber.org/protocol/feature-neg"); + + x = xmlnode_new_child(feature, "x"); + xmlnode_set_namespace(x, "jabber:x:data"); + xmlnode_set_attrib(x, "type", "submit"); + + field = xmlnode_new_child(x, "field"); + xmlnode_set_attrib(field, "var", "stream-method"); + + value = xmlnode_new_child(field, "value"); + xmlnode_insert_data(value, "http://jabber.org/protocol/bytestreams", -1); + + xep_iq_send_and_free(iq); +} + +static void +bonjour_free_xfer(PurpleXfer *xfer) +{ + XepXfer *xf = NULL; + BonjourData *bd = NULL; + + if(xfer == NULL) { + purple_debug_info("bonjour", "bonjour-free-xfer-null.\n"); + return; + } + + purple_debug_info("bonjour", "bonjour-free-xfer-%p.\n", xfer); + + xf = (XepXfer*)xfer->data; + if(xf != NULL) { + bd = (BonjourData*)xf->data; + if(bd != NULL) { + bd->xfer_lists = g_list_remove(bd->xfer_lists, xfer); + purple_debug_info("bonjour", "B free xfer from lists(%p).\n", bd->xfer_lists); + } + if (xf->proxy_connection != NULL) + purple_proxy_connect_cancel(xf->proxy_connection); + if (xf->listen_data != NULL) + purple_network_listen_cancel(xf->listen_data); + g_free(xf->iq_id); + g_free(xf->jid); + g_free(xf->proxy_host); + g_free(xf->buddy_ip); + g_free(xf->sid); + g_free(xf); + xfer->data = NULL; + } + + purple_debug_info("bonjour", "Need close socket=%d.\n", xfer->fd); +} + +PurpleXfer * +bonjour_new_xfer(PurpleConnection *gc, const char *who) +{ + PurpleXfer *xfer; + XepXfer *xep_xfer = NULL; + BonjourData *bd = NULL; + + if(who == NULL || gc == NULL) + return NULL; + + purple_debug_info("bonjour", "Bonjour-new-xfer to %s.\n", who); + bd = (BonjourData*) gc->proto_data; + if(bd == NULL) + return NULL; + + /* Build the file transfer handle */ + xfer = purple_xfer_new(gc->account, PURPLE_XFER_SEND, who); + xfer->data = xep_xfer = g_new0(XepXfer, 1); + xep_xfer->data = bd; + + purple_debug_info("bonjour", "Bonjour-new-xfer bd=%p data=%p.\n", bd, xep_xfer->data); + + /* We don't support IBB yet */ + /*xep_xfer->mode = XEP_BYTESTREAMS | XEP_IBB;*/ + xep_xfer->mode = XEP_BYTESTREAMS; + xep_xfer->sid = NULL; + + purple_xfer_set_init_fnc(xfer, bonjour_xfer_init); + purple_xfer_set_cancel_send_fnc(xfer, bonjour_xfer_cancel_send); + purple_xfer_set_end_fnc(xfer, bonjour_xfer_end); + + bd->xfer_lists = g_list_append(bd->xfer_lists, xfer); + + return xfer; +} + +void +bonjour_send_file(PurpleConnection *gc, const char *who, const char *file) +{ + PurpleXfer *xfer = NULL; + + g_return_if_fail(gc != NULL); + g_return_if_fail(who != NULL); + + purple_debug_info("bonjour", "Bonjour-send-file to=%s.\n", who); + + xfer = bonjour_new_xfer(gc, who); + + if (file) + purple_xfer_request_accepted(xfer, file); + else + purple_xfer_request(xfer); + +} + +static void +bonjour_xfer_init(PurpleXfer *xfer) +{ + PurpleBuddy *buddy = NULL; + BonjourBuddy *bd = NULL; + XepXfer *xf = NULL; + + xf = (XepXfer*)xfer->data; + if(xf == NULL) + return; + + purple_debug_info("bonjour", "Bonjour-xfer-init.\n"); + + buddy = purple_find_buddy(xfer->account, xfer->who); + /* this buddy is offline. */ + if (buddy == NULL) + return; + + bd = (BonjourBuddy *)buddy->proto_data; + xf->buddy_ip = g_strdup(bd->ip); + if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND) { + /* initiate file transfer, send SI offer. */ + purple_debug_info("bonjour", "Bonjour xfer type is PURPLE_XFER_SEND.\n"); + xep_ft_si_offer(xfer, xfer->who); + } else { + /* accept file transfer request, send SI result. */ + xep_ft_si_result(xfer, xfer->who); + purple_debug_info("bonjour", "Bonjour xfer type is PURPLE_XFER_RECEIVE.\n"); + } +} + +void +xep_si_parse(PurpleConnection *pc, xmlnode *packet, PurpleBuddy *pb) +{ + const char *type, *id; + BonjourData *bd = NULL; + PurpleXfer *xfer = NULL; + + if(pc == NULL || packet == NULL || pb == NULL) + return; + bd = (BonjourData*) pc->proto_data; + if(bd == NULL) + return; + + purple_debug_info("bonjour", "xep-si-parse.\n"); + + type = xmlnode_get_attrib(packet, "type"); + id = xmlnode_get_attrib(packet, "id"); + if(type) { + if(!strcmp(type, "set")) { + const char *profile; + xmlnode *si; + gboolean parsed_receive = FALSE; + + si = xmlnode_get_child(packet, "si"); + + purple_debug_info("bonjour", "si offer Message type - SET.\n"); + if (si && (profile = xmlnode_get_attrib(si, "profile")) + && !strcmp(profile, "http://jabber.org/protocol/si/profile/file-transfer")) { + const char *filename = NULL, *filesize_str = NULL; + int filesize = 0; + xmlnode *file; + + const char *sid = xmlnode_get_attrib(si, "id"); + + if ((file = xmlnode_get_child(si, "file"))) { + filename = xmlnode_get_attrib(file, "name"); + if((filesize_str = xmlnode_get_attrib(file, "size"))) + filesize = atoi(filesize_str); + } + + /* TODO: Make sure that it is advertising a bytestreams transfer */ + + bonjour_xfer_receive(pc, id, sid, pb->name, filesize, filename, XEP_BYTESTREAMS); + + parsed_receive = TRUE; + } + + if (!parsed_receive) { + purple_debug_info("bonjour", "rejecting unrecognized si SET offer.\n"); + xep_ft_si_reject((BonjourData *)pc->proto_data, id, pb->name, "403", "cancel"); + /*TODO: Send Cancel (501) */ + } + } else if(!strcmp(type, "result")) { + purple_debug_info("bonjour", "si offer Message type - RESULT.\n"); + + xfer = bonjour_si_xfer_find(bd, id, pb->name); + + if(xfer == NULL) { + purple_debug_info("bonjour", "xfer find fail.\n"); + xep_ft_si_reject((BonjourData *)pc->proto_data, id, pb->name, "403", "cancel"); + } else + bonjour_bytestreams_init(xfer); + + } else if(!strcmp(type, "error")) { + purple_debug_info("bonjour", "si offer Message type - ERROR.\n"); + + xfer = bonjour_si_xfer_find(bd, id, pb->name); + + if(xfer == NULL) + purple_debug_info("bonjour", "xfer find fail.\n"); + else + purple_xfer_cancel_remote(xfer); + } else + purple_debug_info("bonjour", "si offer Message type - Unknown-%d.\n", type); + } +} + +void +xep_bytestreams_parse(PurpleConnection *pc, xmlnode *packet, PurpleBuddy *pb) +{ + const char *type = NULL, *from = NULL; + xmlnode *query = NULL, *streamhost = NULL; + BonjourData *bd = NULL; + PurpleXfer *xfer = NULL; + XepXfer *xf = NULL; + int portnum; + + if(pc == NULL || packet == NULL || pb == NULL) + return; + + bd = (BonjourData*) pc->proto_data; + if(bd == NULL) + return; + + purple_debug_info("bonjour", "xep-bytestreams-parse.\n"); + + type = xmlnode_get_attrib(packet, "type"); + from = pb->name; + query = xmlnode_get_child(packet,"query"); + if(type) { + if(!strcmp(type, "set")) { + const char *iq_id, *sid; + gboolean found = FALSE; + + purple_debug_info("bonjour", "bytestream offer Message type - SET.\n"); + + iq_id = xmlnode_get_attrib(packet, "id"); + + sid = xmlnode_get_attrib(query, "sid"); + xfer = bonjour_si_xfer_find(bd, sid, from); + + if(xfer) { + const char *jid, *host, *port; + + xf = (XepXfer*)xfer->data; + for(streamhost = xmlnode_get_child(query, "streamhost"); + streamhost; + streamhost = xmlnode_get_next_twin(streamhost)) { + + if((jid = xmlnode_get_attrib(streamhost, "jid")) && + (host = xmlnode_get_attrib(streamhost, "host")) && + (port = xmlnode_get_attrib(streamhost, "port")) && + (portnum = atoi(port))) { + + if(!strcmp(host, xf->buddy_ip)) { + g_free(xf->iq_id); + xf->iq_id = g_strdup(iq_id); + xf->jid = g_strdup(jid); + xf->proxy_host = g_strdup(host); + xf->proxy_port = portnum; + purple_debug_info("bonjour", "bytestream offer parse" + "jid=%s host=%s port=%d.\n", jid, host, portnum); + bonjour_bytestreams_connect(xfer, pb); + found = TRUE; + break; + } + } else { + purple_debug_info("bonjour", "bytestream offer Message parse error.\n"); + } + } + } else { + + } + + if (!found) { + purple_debug_error("bonjour", "Didn't find an acceptable streamhost.\n"); + + if (iq_id && xfer != NULL) + xep_ft_si_reject(bd, iq_id, xfer->who, "404", "cancel"); + } + + } else { + purple_debug_info("bonjour", "bytestream offer Message type - Unknown-%s.\n", type); + } + } +} + +static void +bonjour_xfer_receive(PurpleConnection *pc, const char *id, const char *sid, const char *from, + const int filesize, const char *filename, int option) +{ + PurpleXfer *xfer = NULL; + XepXfer *xf = NULL; + BonjourData *bd = NULL; + + if(pc == NULL || id == NULL || from == NULL) + return; + + bd = (BonjourData*) pc->proto_data; + if(bd == NULL) + return; + + purple_debug_info("bonjour", "bonjour-xfer-receive.\n"); + + /* Build the file transfer handle */ + xfer = purple_xfer_new(pc->account, PURPLE_XFER_RECEIVE, from); + xfer->data = xf = g_new0(XepXfer, 1); + xf->data = bd; + purple_xfer_set_filename(xfer, filename); + xf->iq_id = g_strdup(id); + xf->sid = g_strdup(sid); + + if(filesize > 0) + purple_xfer_set_size(xfer, filesize); + purple_xfer_set_init_fnc(xfer, bonjour_xfer_init); + purple_xfer_set_request_denied_fnc(xfer, bonjour_xfer_request_denied); + purple_xfer_set_cancel_recv_fnc(xfer, bonjour_xfer_cancel_recv); + purple_xfer_set_end_fnc(xfer, bonjour_xfer_end); + + bd->xfer_lists = g_list_append(bd->xfer_lists, xfer); + + purple_xfer_request(xfer); +} + +static void +bonjour_sock5_request_cb(gpointer data, gint source, PurpleInputCondition cond) +{ + PurpleXfer *xfer = data; + XepXfer *xf = NULL; + int acceptfd; + int len = 0; + + xf = xfer->data; + if(xf == NULL) + return; + + purple_debug_info("bonjour", "bonjour_sock5_request_cb - req_state = 0x%x\n", xf->sock5_req_state); + + switch(xf->sock5_req_state){ + case 0x00: + acceptfd = accept(source, NULL, 0); + if(acceptfd == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { + + } else if(acceptfd == -1) { + /* This should cancel the ft */ + purple_debug_error("bonjour", "Error accepting incoming SOCKS5 connection. (%d)\n", errno); + + purple_input_remove(xfer->watcher); + xfer->watcher = 0; + close(source); + purple_xfer_cancel_remote(xfer); + return; + } else { + int flags; + + purple_debug_info("bonjour", "Accepted SOCKS5 ft connection - fd=%d\n", acceptfd); + + flags = fcntl(acceptfd, F_GETFL); + fcntl(acceptfd, F_SETFL, flags | O_NONBLOCK); + + purple_input_remove(xfer->watcher); + close(source); + xfer->watcher = purple_input_add(acceptfd, PURPLE_INPUT_READ, + bonjour_sock5_request_cb, xfer); + xf->sock5_req_state++; + xf->rxlen = 0; + } + break; + case 0x01: + xfer->fd = source; + len = read(source, xf->rx_buf + xf->rxlen, 3); + if(len < 0 && errno == EAGAIN) + return; + else if(len <= 0){ + purple_input_remove(xfer->watcher); + xfer->watcher = 0; + close(source); + purple_xfer_cancel_remote(xfer); + return; + } else { + purple_input_remove(xfer->watcher); + xfer->watcher = purple_input_add(source, PURPLE_INPUT_WRITE, + bonjour_sock5_request_cb, xfer); + xf->sock5_req_state++; + xf->rxlen = 0; + bonjour_sock5_request_cb(xfer, source, PURPLE_INPUT_WRITE); + } + break; + case 0x02: + xf->tx_buf[0] = 0x05; + xf->tx_buf[1] = 0x00; + len = write(source, xf->tx_buf, 2); + if (len < 0 && errno == EAGAIN) + return; + else if (len < 0) { + purple_input_remove(xfer->watcher); + xfer->watcher = 0; + close(source); + purple_xfer_cancel_remote(xfer); + return; + } else { + purple_input_remove(xfer->watcher); + xfer->watcher = purple_input_add(source, PURPLE_INPUT_READ, + bonjour_sock5_request_cb, xfer); + xf->sock5_req_state++; + xf->rxlen = 0; + } + break; + case 0x03: + len = read(source, xf->rx_buf + xf->rxlen, 20); + if(len<=0){ + } else { + purple_input_remove(xfer->watcher); + xfer->watcher = purple_input_add(source, PURPLE_INPUT_WRITE, + bonjour_sock5_request_cb, xfer); + xf->sock5_req_state++; + xf->rxlen = 0; + bonjour_sock5_request_cb(xfer, source, PURPLE_INPUT_WRITE); + } + break; + case 0x04: + xf->tx_buf[0] = 0x05; + xf->tx_buf[1] = 0x00; + xf->tx_buf[2] = 0x00; + xf->tx_buf[3] = 0x03; + xf->tx_buf[4] = strlen(xf->buddy_ip); + memcpy(xf->tx_buf + 5, xf->buddy_ip, strlen(xf->buddy_ip)); + xf->tx_buf[5+strlen(xf->buddy_ip)] = 0x00; + xf->tx_buf[6+strlen(xf->buddy_ip)] = 0x00; + len = write(source, xf->tx_buf, 7 + strlen(xf->buddy_ip)); + if (len < 0 && errno == EAGAIN) { + return; + } else if (len < 0) { + purple_input_remove(xfer->watcher); + xfer->watcher = 0; + close(source); + purple_xfer_cancel_remote(xfer); + return; + } else { + purple_input_remove(xfer->watcher); + xfer->watcher = 0; + xf->rxlen = 0; + /*close(source);*/ + purple_xfer_start(xfer, source, NULL, -1); + } + break; + default: + break; + } + return; +} + +static void +bonjour_bytestreams_listen(int sock, gpointer data) +{ + PurpleXfer *xfer = data; + XepXfer *xf; + XepIq *iq; + xmlnode *query, *streamhost; + char *port; + const char *next_ip; + const char *local_ip = NULL; + char token [] = ";"; + BonjourData *bd; + + purple_debug_info("bonjour", "Bonjour-bytestreams-listen. sock=%d.\n", sock); + if (sock < 0 || xfer == NULL) { + /*purple_xfer_cancel_local(xfer);*/ + return; + } + + xfer->watcher = purple_input_add(sock, PURPLE_INPUT_READ, + bonjour_sock5_request_cb, xfer); + xf = (XepXfer*)xfer->data; + xf->listen_data = NULL; + + bd = xf->data; + + iq = xep_iq_new(bd, XEP_IQ_SET, xfer->who, purple_account_get_username(bd->jabber_data->account), xf->sid); + + query = xmlnode_new_child(iq->node, "query"); + xmlnode_set_namespace(query, "http://jabber.org/protocol/bytestreams"); + xmlnode_set_attrib(query, "sid", xf->sid); + xmlnode_set_attrib(query, "mode", "tcp"); + + xfer->local_port = purple_network_get_port_from_fd(sock); + + local_ip = purple_network_get_my_ip_ext2(sock); + /* cheat a little here - the intent of the "const" attribute is to make it clear that the string doesn't need to be freed */ + next_ip = strtok((char *)local_ip, token); + + port = g_strdup_printf("%hu", xfer->local_port); + while(next_ip != NULL) { + streamhost = xmlnode_new_child(query, "streamhost"); + xmlnode_set_attrib(streamhost, "jid", xf->sid); + xmlnode_set_attrib(streamhost, "host", next_ip); + xmlnode_set_attrib(streamhost, "port", port); + next_ip = strtok(NULL, token); + } + g_free(port); + + xep_iq_send_and_free(iq); +} + +static void +bonjour_bytestreams_init(PurpleXfer *xfer) +{ + XepXfer *xf = NULL; + if(xfer == NULL) + return; + purple_debug_info("bonjour", "Bonjour-bytestreams-init.\n"); + xf = xfer->data; + purple_network_listen_map_external(FALSE); + xf->listen_data = purple_network_listen_range(0, 0, SOCK_STREAM, + bonjour_bytestreams_listen, xfer); + purple_network_listen_map_external(TRUE); + if (xf->listen_data == NULL) { + purple_xfer_cancel_local(xfer); + } + return; +} + +static void +bonjour_bytestreams_connect_cb(gpointer data, gint source, const gchar *error_message) +{ + PurpleXfer *xfer = data; + XepXfer *xf = xfer->data; + XepIq *iq = NULL; + xmlnode *q_node, *tmp_node; + BonjourData *bd; + + if(source < 0) { + purple_debug_error("bonjour", "Error connecting via SOCKS5 - %s\n", + error_message ? error_message : "(null)"); + xep_ft_si_reject(xf->data, xf->iq_id, xfer->who, "404", "cancel"); + /* Cancel the connection */ + purple_xfer_cancel_local(xfer); + return; + } + + purple_debug_info("bonjour", "Connected successfully via SOCKS5, starting transfer.\n"); + + bd = xf->data; + + purple_proxy_info_destroy(xf->proxy_info); + xf->proxy_connection = NULL; + xf->proxy_info = NULL; + /* Here, start the file transfer.*/ + + /* Notify Initiator of Connection */ + iq = xep_iq_new(bd, XEP_IQ_RESULT, xfer->who, purple_account_get_username(bd->jabber_data->account), xf->iq_id); + q_node = xmlnode_new_child(iq->node, "query"); + xmlnode_set_namespace(q_node, "http://jabber.org/protocol/bytestreams"); + tmp_node = xmlnode_new_child(q_node, "streamhost-used"); + xmlnode_set_attrib(tmp_node, "jid", xf->jid); + xep_iq_send_and_free(iq); + + purple_xfer_start(xfer, source, NULL, -1); +} + +static void +bonjour_bytestreams_connect(PurpleXfer *xfer, PurpleBuddy *pb) +{ + XepXfer *xf = NULL; + char dstaddr[41]; + unsigned char hashval[20]; + char *p = NULL; + int i; + + if(xfer == NULL) + return; + + purple_debug_info("bonjour", "bonjour-bytestreams-connect.\n"); + + xf = (XepXfer*)xfer->data; + if(!xf) + return; + + p = g_strdup_printf("%s%s%s", xf->sid, pb->name, purple_account_get_username(pb->account)); + purple_cipher_digest_region("sha1", (guchar *)p, strlen(p), + sizeof(hashval), hashval, NULL); + g_free(p); + + memset(dstaddr, 0, 41); + p = dstaddr; + for(i = 0; i < 20; i++, p += 2) + snprintf(p, 3, "%02x", hashval[i]); + + xf->proxy_info = purple_proxy_info_new(); + purple_proxy_info_set_type(xf->proxy_info, PURPLE_PROXY_SOCKS5); + purple_proxy_info_set_host(xf->proxy_info, xf->proxy_host); + purple_proxy_info_set_port(xf->proxy_info, xf->proxy_port); + xf->proxy_connection = purple_proxy_connect_socks5(NULL, xf->proxy_info, + dstaddr, 0, + bonjour_bytestreams_connect_cb, xfer); + + if(xf->proxy_connection == NULL) { + xep_ft_si_reject(xf->data, xf->iq_id, xfer->who, "404", "cancel"); + /* Cancel the connection */ + purple_xfer_cancel_local(xfer); + /*purple_proxy_info_destroy(xf->proxy_info); + xf->proxy_info = NULL;*/ + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/bonjour/bonjour_ft.h Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,75 @@ +/* + * purple - Bonjour Protocol Plugin + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef _BONJOUR_FT_H_ +#define _BONJOUR_FT_H_ +#include "network.h" +#include "proxy.h" +typedef struct _XepXfer XepXfer; +typedef enum { + XEP_BYTESTREAMS = 1, + XEP_IBB = 2, + XEP_UNKNOWN = 4, +} XepSiMode; + +struct _XepXfer +{ + void *data; + char *filename; + int filesize; + char *iq_id; + char *sid; + char *recv_id; + char *buddy_ip; + int mode; + PurpleNetworkListenData *listen_data; + int sock5_req_state; + int rxlen; + char rx_buf[0x500]; + char tx_buf[0x500]; + PurpleProxyInfo *proxy_info; + PurpleProxyConnectData *proxy_connection; + char *jid; + char *proxy_host; + int proxy_port; +}; + +/** + * Create a new PurpleXfer + * + * @param gc The PurpleConnection handle. + * @param who Who will we be sending it to? + */ +PurpleXfer *bonjour_new_xfer(PurpleConnection *gc, const char *who); + +/** + * Send a file. + * + * @param gc The PurpleConnection handle. + * @param who Who are we sending it to? + * @param file What file? If NULL, user will choose after this call. + */ +void bonjour_send_file(PurpleConnection *gc, const char *who, const char *file); + +void xep_si_parse(PurpleConnection *pc, xmlnode *packet, PurpleBuddy *pb); +void +xep_bytestreams_parse(PurpleConnection *pc, xmlnode *packet, PurpleBuddy *pb); +#endif
--- a/libpurple/protocols/bonjour/buddy.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/bonjour/buddy.c Sat Nov 17 02:20:01 2007 +0000 @@ -62,9 +62,11 @@ } void -set_bonjour_buddy_value(BonjourBuddy* buddy, const char *record_key, const char *value, uint32_t len){ +set_bonjour_buddy_value(BonjourBuddy* buddy, const char *record_key, const char *value, guint32 len){ gchar **fld = NULL; + g_return_if_fail(record_key != NULL); + if (!strcmp(record_key, "1st")) fld = &buddy->first; else if(!strcmp(record_key, "email"))
--- a/libpurple/protocols/bonjour/buddy.h Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/bonjour/buddy.h Sat Nov 17 02:20:01 2007 +0000 @@ -83,7 +83,7 @@ /** * Sets a value in the BonjourBuddy struct, destroying the old value */ -void set_bonjour_buddy_value(BonjourBuddy *buddy, const char *record_key, const char *value, uint32_t len); +void set_bonjour_buddy_value(BonjourBuddy *buddy, const char *record_key, const char *value, guint32 len); /** * Check if all the compulsory buddy data is present.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/bonjour/dns_sd_proxy.c Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,181 @@ +/* + * + * 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 Library 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 "win32dep.h" +#include "dns_sd_proxy.h" + +#ifndef LINK_DNS_SD_DIRECTLY +static DNSServiceErrorType (DNSSD_API* _DNSServiceAddRecord)(DNSServiceRef sdRef, DNSRecordRef *RecordRef, DNSServiceFlags flags, + uint16_t rrtype, uint16_t rdlen, const void *rdata, uint32_t ttl); +static DNSServiceErrorType (DNSSD_API* _DNSServiceBrowse)(DNSServiceRef *sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, + const char *regtype, const char *domain, DNSServiceBrowseReply callBack, void *context); +static int (DNSSD_API* _DNSServiceConstructFullName)(char *fullName, const char *service, const char *regtype, const char *domain); +static DNSServiceErrorType (DNSSD_API* _DNSServiceProcessResult)(DNSServiceRef sdRef); +static DNSServiceErrorType (DNSSD_API* _DNSServiceQueryRecord)(DNSServiceRef *sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, + const char *fullname, uint16_t rrtype, uint16_t rrclass, DNSServiceQueryRecordReply callBack, void *context); +static void (DNSSD_API* _DNSServiceRefDeallocate)(DNSServiceRef sdRef); +static int (DNSSD_API* _DNSServiceRefSockFD)(DNSServiceRef sdRef); +static DNSServiceErrorType (DNSSD_API* _DNSServiceRegister)(DNSServiceRef *sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, + const char *name, const char *regtype, const char *domain, const char *host, uint16_t port, uint16_t txtLen, + const void *txtRecord, DNSServiceRegisterReply callBack, void *context); +static DNSServiceErrorType (DNSSD_API* _DNSServiceResolve)(DNSServiceRef *sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, const char *name, + const char *regtype, const char *domain, DNSServiceResolveReply callBack, void *context); +static DNSServiceErrorType (DNSSD_API* _DNSServiceRemoveRecord)(DNSServiceRef sdRef, DNSRecordRef RecordRef, DNSServiceFlags flags); +static DNSServiceErrorType (DNSSD_API* _DNSServiceUpdateRecord)(DNSServiceRef sdRef, DNSRecordRef RecordRef, DNSServiceFlags flags, + uint16_t rdlen, const void *rdata, uint32_t ttl); +static void (DNSSD_API* _TXTRecordCreate)(TXTRecordRef *txtRecord, uint16_t bufferLen, void *buffer); +static void (DNSSD_API* _TXTRecordDeallocate)(TXTRecordRef *txtRecord); +static const void * (DNSSD_API* _TXTRecordGetBytesPtr)(const TXTRecordRef *txtRecord); +static int16_t (DNSSD_API* _TXTRecordGetLength)(const TXTRecordRef *txtRecord); +static const void * (DNSSD_API* _TXTRecordGetValuePtr)(uint16_t txtLen, const void *txtRecord, const char *key, uint8_t *valueLen); +static DNSServiceErrorType (DNSSD_API* _TXTRecordSetValue)(TXTRecordRef *txtRecord, const char *key, uint8_t valueSize, const void *value); +#endif + +gboolean dns_sd_available(void) { +#ifndef LINK_DNS_SD_DIRECTLY + static gboolean initialized = FALSE; + static gboolean loaded = FALSE; + + if (!initialized) { + initialized = TRUE; + if ((_DNSServiceAddRecord = (void *) wpurple_find_and_loadproc("dnssd.dll", "DNSServiceAddRecord")) + && (_DNSServiceBrowse = (void *) wpurple_find_and_loadproc("dnssd.dll", "DNSServiceBrowse")) + && (_DNSServiceConstructFullName = (void *) wpurple_find_and_loadproc("dnssd.dll", "DNSServiceConstructFullName")) + && (_DNSServiceProcessResult = (void *) wpurple_find_and_loadproc("dnssd.dll", "DNSServiceProcessResult")) + && (_DNSServiceQueryRecord = (void *) wpurple_find_and_loadproc("dnssd.dll", "DNSServiceQueryRecord")) + && (_DNSServiceRefDeallocate = (void *) wpurple_find_and_loadproc("dnssd.dll", "DNSServiceRefDeallocate")) + && (_DNSServiceRefSockFD = (void *) wpurple_find_and_loadproc("dnssd.dll", "DNSServiceRefSockFD")) + && (_DNSServiceRegister = (void *) wpurple_find_and_loadproc("dnssd.dll", "DNSServiceRegister")) + && (_DNSServiceResolve = (void *) wpurple_find_and_loadproc("dnssd.dll", "DNSServiceResolve")) + && (_DNSServiceRemoveRecord = (void *) wpurple_find_and_loadproc("dnssd.dll", "DNSServiceRemoveRecord")) + && (_DNSServiceUpdateRecord = (void *) wpurple_find_and_loadproc("dnssd.dll", "DNSServiceUpdateRecord")) + && (_TXTRecordCreate = (void *) wpurple_find_and_loadproc("dnssd.dll", "TXTRecordCreate")) + && (_TXTRecordDeallocate = (void *) wpurple_find_and_loadproc("dnssd.dll", "TXTRecordDeallocate")) + && (_TXTRecordGetBytesPtr = (void *) wpurple_find_and_loadproc("dnssd.dll", "TXTRecordGetBytesPtr")) + && (_TXTRecordGetLength = (void *) wpurple_find_and_loadproc("dnssd.dll", "TXTRecordGetLength")) + && (_TXTRecordGetValuePtr = (void *) wpurple_find_and_loadproc("dnssd.dll", "TXTRecordGetValuePtr")) + && (_TXTRecordSetValue = (void *) wpurple_find_and_loadproc("dnssd.dll", "TXTRecordSetValue"))) { + loaded = TRUE; + } + } + return loaded; +#else + return TRUE; +#endif +} + +#ifndef LINK_DNS_SD_DIRECTLY + +DNSServiceErrorType _wpurple_DNSServiceAddRecord(DNSServiceRef sdRef, DNSRecordRef *RecordRef, DNSServiceFlags flags, + uint16_t rrtype, uint16_t rdlen, const void *rdata, uint32_t ttl) { + g_return_val_if_fail(_DNSServiceAddRecord != NULL, kDNSServiceErr_Unknown); + return (_DNSServiceAddRecord)(sdRef, RecordRef, flags, rrtype, rdlen, rdata, ttl); +} + +DNSServiceErrorType _wpurple_DNSServiceBrowse(DNSServiceRef *sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, + const char *regtype, const char *domain, DNSServiceBrowseReply callBack, void *context) { + g_return_val_if_fail(_DNSServiceBrowse != NULL, kDNSServiceErr_Unknown); + return (_DNSServiceBrowse)(sdRef, flags, interfaceIndex, regtype, domain, callBack, context); +} + +int _wpurple_DNSServiceConstructFullName(char *fullName, const char *service, const char *regtype, const char *domain) { + g_return_val_if_fail(_DNSServiceConstructFullName != NULL, 0); + return (_DNSServiceConstructFullName)(fullName, service, regtype, domain); +} + +DNSServiceErrorType _wpurple_DNSServiceProcessResult(DNSServiceRef sdRef) { + g_return_val_if_fail(_DNSServiceProcessResult != NULL, kDNSServiceErr_Unknown); + return (_DNSServiceProcessResult)(sdRef); +} + + +DNSServiceErrorType _wpurple_DNSServiceQueryRecord(DNSServiceRef *sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, + const char *fullname, uint16_t rrtype, uint16_t rrclass, DNSServiceQueryRecordReply callBack, void *context) { + g_return_val_if_fail(_DNSServiceQueryRecord != NULL, kDNSServiceErr_Unknown); + return (_DNSServiceQueryRecord)(sdRef, flags, interfaceIndex, fullname, rrtype, rrclass, callBack, context); +} + +void _wpurple_DNSServiceRefDeallocate(DNSServiceRef sdRef) { + g_return_if_fail(_DNSServiceRefDeallocate != NULL); + (_DNSServiceRefDeallocate)(sdRef); +} + +int _wpurple_DNSServiceRefSockFD(DNSServiceRef sdRef) { + g_return_val_if_fail(_DNSServiceRefSockFD != NULL, -1); + return (_DNSServiceRefSockFD)(sdRef); +} + +DNSServiceErrorType _wpurple_DNSServiceRegister(DNSServiceRef *sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, + const char *name, const char *regtype, const char *domain, const char *host, uint16_t port, uint16_t txtLen, + const void *txtRecord, DNSServiceRegisterReply callBack, void *context) { + g_return_val_if_fail(_DNSServiceRegister != NULL, kDNSServiceErr_Unknown); + return (_DNSServiceRegister)(sdRef, flags, interfaceIndex, name, regtype, domain, host, port, txtLen, txtRecord, callBack, context); +} + +DNSServiceErrorType _wpurple_DNSServiceResolve(DNSServiceRef *sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, const char *name, + const char *regtype, const char *domain, DNSServiceResolveReply callBack, void *context) { + g_return_val_if_fail(_DNSServiceResolve != NULL, kDNSServiceErr_Unknown); + return (_DNSServiceResolve)(sdRef, flags, interfaceIndex, name, regtype, domain, callBack, context); +} + +DNSServiceErrorType _wpurple_DNSServiceRemoveRecord(DNSServiceRef sdRef, DNSRecordRef RecordRef, DNSServiceFlags flags) { + g_return_val_if_fail(_DNSServiceRemoveRecord != NULL, kDNSServiceErr_Unknown); + return (_DNSServiceRemoveRecord)(sdRef, RecordRef, flags); +} + +DNSServiceErrorType _wpurple_DNSServiceUpdateRecord(DNSServiceRef sdRef, DNSRecordRef RecordRef, DNSServiceFlags flags, + uint16_t rdlen, const void *rdata, uint32_t ttl) { + g_return_val_if_fail(_DNSServiceUpdateRecord != NULL, kDNSServiceErr_Unknown); + return (_DNSServiceUpdateRecord)(sdRef, RecordRef, flags, rdlen, rdata, ttl); +} + +void _wpurple_TXTRecordCreate(TXTRecordRef *txtRecord, uint16_t bufferLen, void *buffer) { + g_return_if_fail(_TXTRecordCreate != NULL); + (_TXTRecordCreate)(txtRecord, bufferLen, buffer); +} + +void _wpurple_TXTRecordDeallocate(TXTRecordRef *txtRecord) { + g_return_if_fail(_TXTRecordDeallocate != NULL); + (_TXTRecordDeallocate)(txtRecord); +} + +const void * _wpurple_TXTRecordGetBytesPtr(const TXTRecordRef *txtRecord) { + g_return_val_if_fail(_TXTRecordGetBytesPtr != NULL, NULL); + return (_TXTRecordGetBytesPtr)(txtRecord); +} + +uint16_t _wpurple_TXTRecordGetLength(const TXTRecordRef *txtRecord) { + g_return_val_if_fail(_TXTRecordGetLength != NULL, 0); + return (_TXTRecordGetLength)(txtRecord); +} + +const void * _wpurple_TXTRecordGetValuePtr(uint16_t txtLen, const void *txtRecord, const char *key, uint8_t *valueLen) { + g_return_val_if_fail(_TXTRecordGetValuePtr != NULL, NULL); + return (_TXTRecordGetValuePtr)(txtLen, txtRecord, key, valueLen); +} + +DNSServiceErrorType _wpurple_TXTRecordSetValue(TXTRecordRef *txtRecord, const char *key, uint8_t valueSize, const void *value) { + g_return_val_if_fail(_TXTRecordSetValue != NULL, kDNSServiceErr_Unknown); + return (_TXTRecordSetValue)(txtRecord, key, valueSize, value); +} + +#endif /*LINK_DNS_SD_DIRECTLY*/ +
--- a/libpurple/protocols/bonjour/dns_sd_proxy.h Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/bonjour/dns_sd_proxy.h Sat Nov 17 02:20:01 2007 +0000 @@ -1,3 +1,23 @@ +/* + * + * 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 Library 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 _DNS_SD_PROXY #define _DNS_SD_PROXY @@ -10,4 +30,85 @@ #include <dns_sd.h> +gboolean dns_sd_available(void); + +#ifndef LINK_DNS_SD_DIRECTLY + +DNSServiceErrorType _wpurple_DNSServiceAddRecord(DNSServiceRef sdRef, DNSRecordRef *RecordRef, DNSServiceFlags flags, + uint16_t rrtype, uint16_t rdlen, const void *rdata, uint32_t ttl); +#define DNSServiceAddRecord(sdRef, RecordRef, flags, rrtype, rdlen, rdata, ttl) \ + _wpurple_DNSServiceAddRecord(sdRef, RecordRef, flags, rrtype, rdlen, rdata, ttl) + +DNSServiceErrorType _wpurple_DNSServiceBrowse(DNSServiceRef *sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, + const char *regtype, const char *domain, DNSServiceBrowseReply callBack, void *context); +#define DNSServiceBrowse(sdRef, flags, interfaceIndex, regtype, domain, callBack, context) \ + _wpurple_DNSServiceBrowse(sdRef, flags, interfaceIndex, regtype, domain, callBack, context) + +int _wpurple_DNSServiceConstructFullName(char *fullName, const char *service, const char *regtype, const char *domain); +#define DNSServiceConstructFullName(fullName, service, regtype, domain) \ + _wpurple_DNSServiceConstructFullName(fullName, service, regtype, domain) + +DNSServiceErrorType _wpurple_DNSServiceProcessResult(DNSServiceRef sdRef); +#define DNSServiceProcessResult(sdRef) \ + _wpurple_DNSServiceProcessResult(sdRef); + +DNSServiceErrorType _wpurple_DNSServiceQueryRecord(DNSServiceRef *sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, + const char *fullname, uint16_t rrtype, uint16_t rrclass, DNSServiceQueryRecordReply callBack, void *context); +#define DNSServiceQueryRecord(sdRef, flags, interfaceIndex, fullname, rrtype, rrclass, callBack, context) \ + _wpurple_DNSServiceQueryRecord(sdRef, flags, interfaceIndex, fullname, rrtype, rrclass, callBack, context) + +void _wpurple_DNSServiceRefDeallocate(DNSServiceRef sdRef); +#define DNSServiceRefDeallocate(sdRef) \ + _wpurple_DNSServiceRefDeallocate(sdRef) + +int _wpurple_DNSServiceRefSockFD(DNSServiceRef sdRef); +#define DNSServiceRefSockFD(sdRef) \ + _wpurple_DNSServiceRefSockFD(sdRef) + +DNSServiceErrorType _wpurple_DNSServiceRegister(DNSServiceRef *sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, + const char *name, const char *regtype, const char *domain, const char *host, uint16_t port, uint16_t txtLen, + const void *txtRecord, DNSServiceRegisterReply callBack, void *context); +#define DNSServiceRegister(sdRef, flags, interfaceIndex, name, regtype, domain, host, port, txtLen, txtRecord, callBack, context) \ + _wpurple_DNSServiceRegister(sdRef, flags, interfaceIndex, name, regtype, domain, host, port, txtLen, txtRecord, callBack, context) + +DNSServiceErrorType _wpurple_DNSServiceResolve(DNSServiceRef *sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, const char *name, + const char *regtype, const char *domain, DNSServiceResolveReply callBack, void *context); +#define DNSServiceResolve(sdRef, flags, interfaceIndex, name, regtype, domain, callBack, context) \ + _wpurple_DNSServiceResolve(sdRef, flags, interfaceIndex, name, regtype, domain, callBack, context) + +DNSServiceErrorType _wpurple_DNSServiceRemoveRecord(DNSServiceRef sdRef, DNSRecordRef RecordRef, DNSServiceFlags flags); +#define DNSServiceRemoveRecord(sdRef, RecordRef, flags) \ + _wpurple_DNSServiceRemoveRecord(sdRef, RecordRef, flags) + +DNSServiceErrorType _wpurple_DNSServiceUpdateRecord(DNSServiceRef sdRef, DNSRecordRef RecordRef, DNSServiceFlags flags, + uint16_t rdlen, const void *rdata, uint32_t ttl); +#define DNSServiceUpdateRecord(sdRef, RecordRef, flags, rdlen, rdata, ttl) \ + _wpurple_DNSServiceUpdateRecord(sdRef, RecordRef, flags, rdlen, rdata, ttl) + +void _wpurple_TXTRecordCreate(TXTRecordRef *txtRecord, uint16_t bufferLen, void *buffer); +#define TXTRecordCreate(txtRecord, bufferLen, buffer) \ + _wpurple_TXTRecordCreate(txtRecord, bufferLen, buffer) + +void _wpurple_TXTRecordDeallocate(TXTRecordRef *txtRecord); +#define TXTRecordDeallocate(txtRecord) \ + _wpurple_TXTRecordDeallocate(txtRecord) + +const void * _wpurple_TXTRecordGetBytesPtr(const TXTRecordRef *txtRecord); +#define TXTRecordGetBytesPtr(txtRecord) \ + _wpurple_TXTRecordGetBytesPtr(txtRecord) + +uint16_t _wpurple_TXTRecordGetLength(const TXTRecordRef *txtRecord); +#define TXTRecordGetLength(txtRecord) \ + _wpurple_TXTRecordGetLength(txtRecord) + +const void * _wpurple_TXTRecordGetValuePtr(uint16_t txtLen, const void *txtRecord, const char *key, uint8_t *valueLen); +#define TXTRecordGetValuePtr(txtLen, txtRecord, key, valueLen) \ + _wpurple_TXTRecordGetValuePtr(txtLen, txtRecord, key, valueLen) + +DNSServiceErrorType _wpurple_TXTRecordSetValue(TXTRecordRef *txtRecord, const char *key, uint8_t valueSize, const void *value); +#define TXTRecordSetValue(txtRecord, key, valueSize, value) \ + _wpurple_TXTRecordSetValue(txtRecord, key, valueSize, value) + +#endif /*LINK_DNS_SD_DIRECTLY*/ + #endif
--- a/libpurple/protocols/bonjour/jabber.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/bonjour/jabber.c Sat Nov 17 02:20:01 2007 +0000 @@ -20,6 +20,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef _WIN32 +#include <net/if.h> +#include <sys/ioctl.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> @@ -45,12 +47,22 @@ #include "parser.h" #include "bonjour.h" #include "buddy.h" +#include "bonjour_ft.h" + +#ifdef _SIZEOF_ADDR_IFREQ +# define HX_SIZE_OF_IFREQ(a) _SIZEOF_ADDR_IFREQ(a) +#else +# define HX_SIZE_OF_IFREQ(a) sizeof(a) +#endif #define STREAM_END "</stream:stream>" /* TODO: specify version='1.0' and send stream features */ #define DOCTYPE "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" \ "<stream:stream xmlns=\"jabber:client\" xmlns:stream=\"http://etherx.jabber.org/streams\" from=\"%s\" to=\"%s\">" +static void +xep_iq_parse(xmlnode *packet, PurpleConnection *connection, PurpleBuddy *pb); + #if 0 /* this isn't used anywhere... */ static const char * _font_size_purple_to_ichat(int size) @@ -77,13 +89,14 @@ #endif static BonjourJabberConversation * -bonjour_jabber_conv_new() { +bonjour_jabber_conv_new(PurpleBuddy *pb) { BonjourJabberConversation *bconv = g_new0(BonjourJabberConversation, 1); bconv->socket = -1; bconv->tx_buf = purple_circ_buffer_new(512); bconv->tx_handler = 0; bconv->rx_handler = 0; + bconv->pb = pb; return bconv; } @@ -244,7 +257,7 @@ return; else if (ret <= 0) { PurpleConversation *conv; - const char *error = strerror(errno); + const char *error = g_strerror(errno); purple_debug_error("bonjour", "Error sending message to buddy %s error: %s\n", purple_buddy_get_name(pb), error ? error : "(null)"); @@ -287,7 +300,7 @@ ret = 0; else if (ret <= 0) { PurpleConversation *conv; - const char *error = strerror(errno); + const char *error = g_strerror(errno); purple_debug_error("bonjour", "Error sending message to buddy %s error: %s\n", purple_buddy_get_name(pb), error ? error : "(null)"); @@ -314,11 +327,15 @@ } void bonjour_jabber_process_packet(PurpleBuddy *pb, xmlnode *packet) { + + g_return_if_fail(packet != NULL); + if (!strcmp(packet->name, "message")) _jabber_parse_and_write_message_to_ui(packet, pb); + else if(!strcmp(packet->name, "iq")) + xep_iq_parse(packet, NULL, pb); else - purple_debug_warning("bonjour", "Unknown packet: %s\n", - packet->name); + purple_debug_warning("bonjour", "Unknown packet: %s\n", packet->name ? packet->name : "(null)"); } @@ -336,8 +353,9 @@ /* There have been an error reading from the socket */ if (errno != EAGAIN) { BonjourBuddy *bb = pb->proto_data; + const char *err = g_strerror(errno); - purple_debug_warning("bonjour", "receive error: %s\n", strerror(errno)); + purple_debug_warning("bonjour", "receive error: %s\n", err ? err : "(null)"); bonjour_jabber_close_conversation(bb->conversation); bb->conversation = NULL; @@ -347,7 +365,7 @@ } return; } else if (len == 0) { /* The other end has closed the socket */ - purple_debug_warning("bonjour", "Connection closed (without stream end) by %s.\n", pb->name); + purple_debug_warning("bonjour", "Connection closed (without stream end) by %s.\n", pb->name ? pb->name : "(null)"); bonjour_jabber_stream_ended(pb); return; } else { @@ -367,7 +385,6 @@ void bonjour_jabber_stream_ended(PurpleBuddy *pb) { BonjourBuddy *bb = pb->proto_data; - PurpleConversation *conv; purple_debug_info("bonjour", "Recieved conversation close notification from %s.\n", pb->name); @@ -375,17 +392,19 @@ /* Inform the user that the conversation has been closed */ if (bb->conversation != NULL) { +#if 0 + PurpleConversation *conv; 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); } +#endif /* Close the socket, clear the watcher and free memory */ bonjour_jabber_close_conversation(bb->conversation); bb->conversation = NULL; } - } void bonjour_jabber_stream_started(PurpleBuddy *pb) { @@ -425,7 +444,7 @@ if (ret == -1 && errno == EAGAIN) return; else if (ret <= 0) { - const char *err = strerror(errno); + const char *err = g_strerror(errno); PurpleConversation *conv; purple_debug_error("bonjour", "Error starting stream with buddy %s at %s:%d error: %s\n", @@ -480,7 +499,7 @@ if (ret == -1 && errno == EAGAIN) ret = 0; else if (ret <= 0) { - const char *err = strerror(errno); + const char *err = g_strerror(errno); purple_debug_error("bonjour", "Error starting stream with buddy %s at %s:%d error: %s\n", purple_buddy_get_name(pb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, err ? err : "(null)"); @@ -521,6 +540,7 @@ struct sockaddr_in their_addr; /* connector's address information */ socklen_t sin_size = sizeof(struct sockaddr); int client_socket; + int flags; BonjourBuddy *bb; char *address_text = NULL; PurpleBuddyList *bl = purple_get_blist(); @@ -533,7 +553,8 @@ if ((client_socket = accept(server_socket, (struct sockaddr *)&their_addr, &sin_size)) == -1) return; - fcntl(client_socket, F_SETFL, O_NONBLOCK); + flags = fcntl(client_socket, F_GETFL); + fcntl(client_socket, F_SETFL, flags | O_NONBLOCK); /* Look for the buddy that has opened the conversation and fill information */ address_text = inet_ntoa(their_addr.sin_addr); @@ -555,7 +576,7 @@ /* Check if the conversation has been previously started */ if (bb->conversation == NULL) { - bb->conversation = bonjour_jabber_conv_new(); + bb->conversation = bonjour_jabber_conv_new(pb); if (!bonjour_jabber_stream_init(pb, client_socket)) { close(client_socket); @@ -579,16 +600,20 @@ /* Open a listening socket for incoming conversations */ if ((data->socket = socket(PF_INET, SOCK_STREAM, 0)) < 0) { - purple_debug_error("bonjour", "Cannot open socket: %s\n", strerror(errno)); - purple_connection_error(data->account->gc, _("Cannot open socket")); + purple_debug_error("bonjour", "Cannot open socket: %s\n", g_strerror(errno)); + purple_connection_error_reason (data->account->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Cannot open socket")); return -1; } /* Make the socket reusable */ if (setsockopt(data->socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) != 0) { - purple_debug_error("bonjour", "Error setting socket options: %s\n", strerror(errno)); - purple_connection_error(data->account->gc, _("Error setting socket options")); + purple_debug_error("bonjour", "Error setting socket options: %s\n", g_strerror(errno)); + purple_connection_error_reason (data->account->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Error setting socket options")); return -1; } @@ -611,16 +636,20 @@ /* On no! We tried 10 ports and could not bind to ANY of them */ if (!bind_successful) { - purple_debug_error("bonjour", "Cannot bind socket: %s\n", strerror(errno)); - purple_connection_error(data->account->gc, _("Could not bind socket to port")); + purple_debug_error("bonjour", "Cannot bind socket: %s\n", g_strerror(errno)); + purple_connection_error_reason (data->account->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Could not bind socket to port")); return -1; } /* Attempt to listen on the bound socket */ if (listen(data->socket, 10) != 0) { - purple_debug_error("bonjour", "Cannot listen on socket: %s\n", strerror(errno)); - purple_connection_error(data->account->gc, _("Could not listen on socket")); + purple_debug_error("bonjour", "Cannot listen on socket: %s\n", g_strerror(errno)); + purple_connection_error_reason (data->account->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Could not listen on socket")); return -1; } @@ -666,7 +695,7 @@ } if (!bonjour_jabber_stream_init(pb, source)) { - const char *err = strerror(errno); + const char *err = g_strerror(errno); PurpleConversation *conv; purple_debug_error("bonjour", "Error starting stream with buddy %s at %s:%d error: %s\n", @@ -685,23 +714,21 @@ } } -int -bonjour_jabber_send_message(BonjourJabber *data, const gchar *to, const gchar *body) +static PurpleBuddy * +_find_or_start_conversation(BonjourJabber *data, const gchar *to) { - xmlnode *message_node, *node, *node2; - gchar *message; - PurpleBuddy *pb; - BonjourBuddy *bb; - int ret; + PurpleBuddy *pb = NULL; + BonjourBuddy *bb = NULL; + + g_return_val_if_fail(data != NULL, NULL); + g_return_val_if_fail(to != NULL, NULL); pb = purple_find_buddy(data->account, to); - if (pb == NULL) { - purple_debug_info("bonjour", "Can't send a message to an offline buddy (%s).\n", to); + if (pb == NULL || pb->proto_data == NULL) /* You can not send a message to an offline buddy */ - return -10000; - } + return NULL; - bb = pb->proto_data; + bb = (BonjourBuddy *) pb->proto_data; /* Check if there is a previously open conversation */ if (bb->conversation == NULL) @@ -709,6 +736,8 @@ PurpleProxyConnectData *connect_data; PurpleProxyInfo *proxy_info; + purple_debug_info("bonjour", "Starting conversation with %s\n", to); + /* Make sure that the account always has a proxy of "none". * This is kind of dirty, but proxy_connect_none() isn't exposed. */ proxy_info = purple_account_get_proxy_info(data->account); @@ -718,21 +747,40 @@ } purple_proxy_info_set_type(proxy_info, PURPLE_PROXY_NONE); - connect_data = - purple_proxy_connect(data->account->gc, data->account, bb->ip, - bb->port_p2pj, _connected_to_buddy, pb); + connect_data = purple_proxy_connect(data->account->gc, data->account, + bb->ip, bb->port_p2pj, _connected_to_buddy, pb); if (connect_data == NULL) { purple_debug_error("bonjour", "Unable to connect to buddy (%s).\n", to); - return -10001; + return NULL; } - bb->conversation = bonjour_jabber_conv_new(); + bb->conversation = bonjour_jabber_conv_new(pb); bb->conversation->connect_data = connect_data; /* We don't want _send_data() to register the tx_handler; * that neeeds to wait until we're actually connected. */ bb->conversation->tx_handler = 0; } + return pb; +} + +int +bonjour_jabber_send_message(BonjourJabber *data, const gchar *to, const gchar *body) +{ + xmlnode *message_node, *node, *node2; + gchar *message; + PurpleBuddy *pb; + BonjourBuddy *bb; + int ret; + + pb = _find_or_start_conversation(data, to); + if (pb == NULL) { + purple_debug_info("bonjour", "Can't send a message to an offline buddy (%s).\n", to); + /* You can not send a message to an offline buddy */ + return -10000; + } + + bb = pb->proto_data; message_node = xmlnode_new("message"); xmlnode_set_attrib(message_node, "to", bb->name); @@ -771,8 +819,25 @@ void bonjour_jabber_close_conversation(BonjourJabberConversation *bconv) { - if (bconv != NULL) - { + if (bconv != NULL) { + GList *xfers, *tmp_next; + BonjourData *bd = bconv->pb->account->gc->proto_data; + + /* Cancel any file transfers that are waiting to begin */ + xfers = bd->xfer_lists; + while(xfers != NULL) { + PurpleXfer *xfer = xfers->data; + tmp_next = xfers->next; + /* We only need to cancel this if it hasn't actually started transferring. */ + /* This will change if we ever support IBB transfers. */ + if (strcmp(xfer->who, bconv->pb->name) == 0 + && (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_NOT_STARTED + || purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_UNKNOWN)) { + purple_xfer_cancel_remote(xfer); + } + xfers = tmp_next; + } + /* Close the socket and remove the watcher */ if (bconv->socket >= 0) { /* Send the end of the stream to the other end of the conversation */ @@ -827,3 +892,169 @@ g_slist_free(buddies); } } + +XepIq * +xep_iq_new(void *data, XepIqType type, const char *to, const char *from, const char *id) +{ + xmlnode *iq_node = NULL; + XepIq *iq = NULL; + + g_return_val_if_fail(data != NULL, NULL); + g_return_val_if_fail(to != NULL, NULL); + g_return_val_if_fail(id != NULL, NULL); + + iq_node = xmlnode_new("iq"); + + xmlnode_set_attrib(iq_node, "to", to); + xmlnode_set_attrib(iq_node, "from", from); + xmlnode_set_attrib(iq_node, "id", id); + switch (type) { + case XEP_IQ_SET: + xmlnode_set_attrib(iq_node, "type", "set"); + break; + case XEP_IQ_GET: + xmlnode_set_attrib(iq_node, "type", "get"); + break; + case XEP_IQ_RESULT: + xmlnode_set_attrib(iq_node, "type", "result"); + break; + case XEP_IQ_ERROR: + xmlnode_set_attrib(iq_node, "type", "error"); + break; + case XEP_IQ_NONE: + default: + xmlnode_set_attrib(iq_node, "type", "none"); + break; + } + + iq = g_new0(XepIq, 1); + iq->node = iq_node; + iq->type = type; + iq->data = ((BonjourData*)data)->jabber_data; + iq->to = (char*)to; + + return iq; +} + +static gboolean +check_if_blocked(PurpleBuddy *pb) +{ + gboolean blocked = FALSE; + GSList *l = NULL; + PurpleAccount *acc = NULL; + + if(pb == NULL) + return FALSE; + + acc = pb->account; + + for(l = acc->deny; l != NULL; l = l->next) { + if(!purple_utf8_strcasecmp(pb->name, (char *)l->data)) { + purple_debug_info("bonjour", "%s has been blocked.\n", pb->name, acc->username); + blocked = TRUE; + break; + } + } + return blocked; +} + +static void +xep_iq_parse(xmlnode *packet, PurpleConnection *connection, PurpleBuddy *pb) +{ + xmlnode *child = NULL; + + if(packet == NULL || pb == NULL) + return; + + if(connection == NULL) { + if(pb->account != NULL) + connection = (pb->account)->gc; + } + + if(check_if_blocked(pb)) + return; + + if ((child = xmlnode_get_child(packet, "si")) || (child = xmlnode_get_child(packet, "error"))) + xep_si_parse(connection, packet, pb); + else + xep_bytestreams_parse(connection, packet, pb); +} + +int +xep_iq_send_and_free(XepIq *iq) +{ + int ret = -1; + PurpleBuddy *pb = NULL; + + /* start the talk, reuse the message socket */ + pb = _find_or_start_conversation ((BonjourJabber*)iq->data, iq->to); + /* Send the message */ + if (pb != NULL) { + /* Convert xml node into stream */ + gchar *msg = xmlnode_to_str(iq->node, NULL); + ret = _send_data(pb, msg); + g_free(msg); + } + + xmlnode_free(iq->node); + iq->node = NULL; + g_free(iq); + + return (ret >= 0) ? 0 : -1; +} + +/* This returns a ';' delimited string containing all non-localhost IPs */ +const char * +purple_network_get_my_ip_ext2(int fd) +{ + char buffer[1024]; + static char ip_ext[17 * 10]; + char *tmp; + char *tip; + struct ifconf ifc; + struct ifreq *ifr; + struct sockaddr_in *sinptr; + guint32 lhost = htonl(127 * 256 * 256 * 256 + 1); + long unsigned int add; + int source = fd; + int len, count = 0; + + if (fd < 0) + source = socket(PF_INET, SOCK_STREAM, 0); + + ifc.ifc_len = sizeof(buffer); + ifc.ifc_req = (struct ifreq *)buffer; + ioctl(source, SIOCGIFCONF, &ifc); + + if (fd < 0) + close(source); + + memset(ip_ext, 0, sizeof(ip_ext)); + memcpy(ip_ext, "0.0.0.0", 7); + tmp = buffer; + tip = ip_ext; + while (tmp < buffer + ifc.ifc_len && count < 10) + { + ifr = (struct ifreq *)tmp; + tmp += HX_SIZE_OF_IFREQ(*ifr); + + if (ifr->ifr_addr.sa_family == AF_INET) + { + sinptr = (struct sockaddr_in *)&ifr->ifr_addr; + if (sinptr->sin_addr.s_addr != lhost) + { + add = ntohl(sinptr->sin_addr.s_addr); + len = g_snprintf(tip, 17, "%lu.%lu.%lu.%lu;", + ((add >> 24) & 255), + ((add >> 16) & 255), + ((add >> 8) & 255), + add & 255); + tip = &tip[len]; + count++; + continue; + } + } + } + + return ip_ext; +}
--- a/libpurple/protocols/bonjour/jabber.h Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/bonjour/jabber.h Sat Nov 17 02:20:01 2007 +0000 @@ -53,6 +53,7 @@ gpointer stream_data; xmlParserCtxt *context; xmlnode *current; + PurpleBuddy *pb; } BonjourJabberConversation; /** @@ -63,7 +64,7 @@ */ gint bonjour_jabber_start(BonjourJabber *data); -int bonjour_jabber_send_message(BonjourJabber *data, const gchar *to, const gchar *body); +int bonjour_jabber_send_message(BonjourJabber *data, const char *to, const char *body); void bonjour_jabber_close_conversation(BonjourJabberConversation *bconv); @@ -75,4 +76,24 @@ void bonjour_jabber_stop(BonjourJabber *data); +typedef enum { + XEP_IQ_SET, + XEP_IQ_GET, + XEP_IQ_RESULT, + XEP_IQ_ERROR, + XEP_IQ_NONE +} XepIqType; + +typedef struct _XepIq { + XepIqType type; + char *id; + xmlnode *node; + char *to; + void *data; +} XepIq; + +XepIq *xep_iq_new(void *data, XepIqType type, const char *to, const char *from, const char *id); +int xep_iq_send_and_free(XepIq *iq); +const char *purple_network_get_my_ip_ext2(int fd); + #endif /* _BONJOUR_JABBER_H_ */
--- a/libpurple/protocols/bonjour/mdns_avahi.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/bonjour/mdns_avahi.c Sat Nov 17 02:20:01 2007 +0000 @@ -81,6 +81,7 @@ /* We've already freed the resolver */ if (r == ((AvahiBuddyImplData *)bb->mdns_impl_data)->resolver) ((AvahiBuddyImplData *)bb->mdns_impl_data)->resolver = NULL; + purple_account_remove_buddy(account, pb, NULL); purple_blist_remove_buddy(pb); } break; @@ -117,9 +118,10 @@ } if (!bonjour_buddy_check(bb)) { - if (pb != NULL) + if (pb != NULL) { + purple_account_remove_buddy(account, pb, NULL); purple_blist_remove_buddy(pb); - else + } else bonjour_buddy_delete(bb); } else /* Add or update the buddy in our buddy list */ @@ -163,8 +165,10 @@ case AVAHI_BROWSER_REMOVE: purple_debug_info("bonjour", "_browser_callback - Remove service\n"); pb = purple_find_buddy(account, name); - if (pb != NULL) + if (pb != NULL) { + purple_account_remove_buddy(account, pb, NULL); purple_blist_remove_buddy(pb); + } break; case AVAHI_BROWSER_ALL_FOR_NOW: case AVAHI_BROWSER_CACHE_EXHAUSTED:
--- a/libpurple/protocols/bonjour/mdns_howl.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/bonjour/mdns_howl.c Sat Nov 17 02:20:01 2007 +0000 @@ -115,7 +115,7 @@ { sw_discovery_resolve_id rid; PurpleAccount *account = (PurpleAccount*)extra; - PurpleBuddy *gb = NULL; + PurpleBuddy *pb = NULL; switch (status) { @@ -148,9 +148,11 @@ break; case SW_DISCOVERY_BROWSE_REMOVE_SERVICE: purple_debug_info("bonjour", "_browser_reply --> Remove service\n"); - gb = purple_find_buddy(account, name); - if (gb != NULL) - purple_blist_remove_buddy(gb); + pb = purple_find_buddy(account, name); + if (pb != NULL) { + purple_account_remove_buddy(account, pb, NULL); + purple_blist_remove_buddy(pb); + } break; case SW_DISCOVERY_BROWSE_RESOLVED: purple_debug_info("bonjour", "_browse_reply --> Resolved\n");
--- a/libpurple/protocols/bonjour/mdns_win32.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/bonjour/mdns_win32.c Sat Nov 17 02:20:01 2007 +0000 @@ -1,4 +1,9 @@ /* + * + * 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 @@ -142,10 +147,13 @@ } /* free the hosts list*/ - g_slist_free(hosts); + while (hosts != NULL) { + hosts = g_slist_remove(hosts, hosts->data); + g_free(hosts->data); + hosts = g_slist_remove(hosts, hosts->data); + } /* free the remaining args memory */ - purple_dnsquery_destroy(args->query); g_free(args->full_service_name); g_free(args); } @@ -230,8 +238,11 @@ /* A peer has sent a goodbye packet, remove them from the buddy list */ purple_debug_info("bonjour", "service browser - remove notification\n"); pb = purple_find_buddy(account, serviceName); - if (pb != NULL) + if (pb != NULL) { + purple_account_remove_buddy(account, pb, NULL); purple_blist_remove_buddy(pb); + } else + purple_debug_warning("bonjour", "Unable to find buddy (%s) to remove\n", serviceName ? serviceName : "(null)"); } }
--- a/libpurple/protocols/bonjour/parser.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/bonjour/parser.c Sat Nov 17 02:20:01 2007 +0000 @@ -64,7 +64,7 @@ char *attrib_ns = NULL; if (attributes[i+2]) { - attrib_ns = g_strdup((char*)attributes[i+2]);; + attrib_ns = g_strdup((char*)attributes[i+2]); } memcpy(attrib, attributes[i+3], attrib_len); @@ -101,7 +101,7 @@ if(!xmlStrcmp(element_name, (xmlChar*) "stream")) { /* Asynchronously close the conversation to prevent bonjour_parser_setup() * being called from within this context */ - g_idle_add(_async_bonjour_jabber_stream_ended_cb, pb); + purple_timeout_add(0, _async_bonjour_jabber_stream_ended_cb, pb); } return; } @@ -188,6 +188,9 @@ { BonjourBuddy *bb = pb->proto_data; + g_return_if_fail(bb != NULL); + g_return_if_fail(bb->conversation != NULL); + if (bb->conversation->context == NULL) { /* libxml inconsistently starts parsing on creating the * parser, so do a ParseChunk right afterwards to force it. */
--- a/libpurple/protocols/gg/buddylist.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/gg/buddylist.c Sat Nov 17 02:20:01 2007 +0000 @@ -27,6 +27,13 @@ #include "gg-utils.h" #include "buddylist.h" +#define F_FIRSTNAME 0 +#define F_LASTNAME 1 +/* #define F_ 2 */ +#define F_NICKNAME 3 +#define F_PHONE 4 +#define F_GROUP 5 +#define F_UIN 6 /* void ggp_buddylist_send(PurpleConnection *gc) {{{ */ void ggp_buddylist_send(PurpleConnection *gc) @@ -90,7 +97,7 @@ gchar **users_tbl; int i; - /* Don't limit a number of records in a buddylist. */ + /* Don't limit the number of records in a buddylist. */ users_tbl = g_strsplit(buddylist, "\r\n", -1); for (i = 0; users_tbl[i] != NULL; i++) { @@ -108,8 +115,8 @@ continue; } - show = charset_convert(data_tbl[3], "CP1250", "UTF-8"); - name = data_tbl[6]; + show = charset_convert(data_tbl[F_NICKNAME], "CP1250", "UTF-8"); + name = data_tbl[F_UIN]; if ('\0' == *name) { purple_debug_warning("gg", "Something is wrong on line %d of the buddylist. Skipping.\n", @@ -121,7 +128,7 @@ show = g_strdup(name); } - purple_debug_info("gg", "got buddy: name=%s show=%s\n", name, show); + purple_debug_info("gg", "got buddy: name=%s; show=%s\n", name, show); if (purple_find_buddy(purple_connection_get_account(gc), name)) { g_free(show); @@ -131,19 +138,19 @@ g = g_strdup("Gadu-Gadu"); - if ('\0' != data_tbl[5]) { + if ('\0' != data_tbl[F_GROUP]) { /* XXX: Probably buddy should be added to all the groups. */ /* Hard limit to at most 50 groups */ - gchar **group_tbl = g_strsplit(data_tbl[5], ",", 50); + gchar **group_tbl = g_strsplit(data_tbl[F_GROUP], ",", 50); if (ggp_array_size(group_tbl) > 0) { g_free(g); - g = g_strdup(group_tbl[0]); + g = charset_convert(group_tbl[0], "CP1250", "UTF-8"); } g_strfreev(group_tbl); } buddy = purple_buddy_new(purple_connection_get_account(gc), name, - strlen(show) ? show : NULL); + strlen(show) ? show : NULL); if (!(group = purple_find_group(g))) { group = purple_group_new(g); @@ -159,7 +166,6 @@ g_strfreev(users_tbl); ggp_buddylist_send(gc); - } /* }}} */ @@ -192,8 +198,7 @@ continue; purple_prpl_got_user_status( - purple_connection_get_account(gc), - buddy->name, "offline", NULL); + account, buddy->name, "offline", NULL); purple_debug_info("gg", "ggp_buddylist_offline: gone: %s\n",
--- a/libpurple/protocols/gg/gg-utils.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/gg/gg-utils.c Sat Nov 17 02:20:01 2007 +0000 @@ -112,5 +112,36 @@ } /* }}} */ +void ggp_status_fake_to_self(PurpleAccount *account) +{ + PurplePresence *presence; + PurpleStatus *status; + const char *status_id; + const char *msg; + + if (! purple_find_buddy(account, purple_account_get_username(account))) + return; + + presence = purple_account_get_presence(account); + status = purple_presence_get_active_status(presence); + msg = purple_status_get_attr_string(status, "message"); + if (msg && !*msg) + msg = NULL; + + status_id = purple_status_get_id(status); + if (strcmp(status_id, "invisible") == 0) { + status_id = "offline"; + } + + if (msg) { + if (strlen(msg) > GG_STATUS_DESCR_MAXSIZE) { + msg = purple_markup_slice(msg, 0, GG_STATUS_DESCR_MAXSIZE); + } + } + purple_prpl_got_user_status(account, purple_account_get_username(account), + status_id, + msg ? "message" : NULL, msg, NULL); +} + /* vim: set ts=8 sts=0 sw=8 noet: */
--- a/libpurple/protocols/gg/gg-utils.h Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/gg/gg-utils.h Sat Nov 17 02:20:01 2007 +0000 @@ -92,6 +92,15 @@ char * ggp_buddy_get_name(PurpleConnection *gc, const uin_t uin); +/** + * Manages the display of account's status in the buddylist. + * + * @param account Current account. + */ +void +ggp_status_fake_to_self(PurpleAccount *account); + + #endif /* _PURPLE_GG_UTILS_H */ /* vim: set ts=8 sts=0 sw=8 noet: */
--- a/libpurple/protocols/gg/gg.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/gg/gg.c Sat Nov 17 02:20:01 2007 +0000 @@ -254,16 +254,14 @@ /* */ /* static void ggp_callback_buddylist_save_ok(PurpleConnection *gc, const char *file) {{{ */ -static void ggp_callback_buddylist_save_ok(PurpleConnection *gc, const char *file) +static void ggp_callback_buddylist_save_ok(PurpleConnection *gc, const char *filename) { PurpleAccount *account = purple_connection_get_account(gc); - FILE *fh; char *buddylist = ggp_buddylist_dump(account); - gchar *msg; purple_debug_info("gg", "Saving...\n"); - purple_debug_info("gg", "file = %s\n", file); + purple_debug_info("gg", "file = %s\n", filename); if (buddylist == NULL) { purple_notify_info(account, _("Save Buddylist..."), @@ -272,21 +270,19 @@ return; } - if ((fh = g_fopen(file, "wb")) == NULL) { - msg = g_strconcat(_("Couldn't open file"), ": ", file, "\n", NULL); - purple_debug_error("gg", "Could not open file: %s\n", file); - purple_notify_error(account, _("Couldn't open file"), msg, NULL); - g_free(msg); - g_free(buddylist); - return; + if(purple_util_write_data_to_file_absolute(filename, buddylist, -1)) { + purple_notify_info(account, _("Save Buddylist..."), + _("Buddylist saved successfully!"), NULL); + } else { + gchar *primary = g_strdup_printf( + _("Couldn't write buddy list for %s to %s"), + purple_account_get_username(account), filename); + purple_notify_error(account, _("Save Buddylist..."), + primary, NULL); + g_free(primary); } - fwrite(buddylist, sizeof(char), g_utf8_strlen(buddylist, -1), fh); - fclose(fh); g_free(buddylist); - - purple_notify_info(account, _("Save Buddylist..."), - _("Buddylist saved successfully!"), NULL); } /* }}} */ @@ -381,12 +377,16 @@ if (email == NULL || p1 == NULL || p2 == NULL || t == NULL || *email == '\0' || *p1 == '\0' || *p2 == '\0' || *t == '\0') { - purple_connection_error(gc, _("Fill in the registration fields.")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_OTHER_ERROR, + _("Fill in the registration fields.")); goto exit_err; } if (g_utf8_collate(p1, p2) != 0) { - purple_connection_error(gc, _("Passwords do not match.")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, + _("Passwords do not match.")); goto exit_err; } @@ -394,7 +394,8 @@ token->id, t); h = gg_register3(email, p1, token->id, t, 0); if (h == NULL || !(s = h->data) || !s->success) { - purple_connection_error(gc, + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_OTHER_ERROR, _("Unable to register new account. Error occurred.\n")); goto exit_err; } @@ -828,6 +829,7 @@ { GGPInfo *info = gc->proto_data; PurpleRequestField *field; + /* TODO: sel may be null. */ GList *sel; field = purple_request_fields_get_field(fields, "name"); @@ -1307,7 +1309,9 @@ if (!(ev = gg_watch_fd(info->session))) { purple_debug_error("gg", "ggp_callback_recv: gg_watch_fd failed -- CRITICAL!\n"); - purple_connection_error(gc, _("Unable to read socket")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Unable to read socket")); return; } @@ -1460,7 +1464,9 @@ if (!(ev = gg_watch_fd(info->session))) { purple_debug_error("gg", "login_handler: gg_watch_fd failed!\n"); - purple_connection_error(gc, _("Unable to read socket")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Unable to read socket")); return; } purple_debug_info("gg", "login_handler: session->fd = %d\n", info->session->fd); @@ -1506,7 +1512,9 @@ case GG_EVENT_CONN_FAILED: purple_input_remove(gc->inpa); gc->inpa = 0; - purple_connection_error(gc, _("Connection failed.")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Connection failed.")); break; default: purple_debug_error("gg", "strange event: %d\n", ev->type); @@ -1712,7 +1720,9 @@ info->session = gg_login(glp); if (info->session == NULL) { - purple_connection_error(gc, _("Connection failed.")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Connection failed.")); g_free(glp); return; } @@ -1868,15 +1878,24 @@ gg_change_status_descr(info->session, new_status_descr, new_msg); g_free(new_msg); } + + ggp_status_fake_to_self(account); + } /* }}} */ /* static void ggp_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group) {{{ */ static void ggp_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group) { + PurpleAccount *account; GGPInfo *info = gc->proto_data; gg_add_notify(info->session, ggp_str_to_uin(buddy->name)); + + account = purple_connection_get_account(gc); + if (strcmp(purple_account_get_username(account), buddy->name) == 0) { + ggp_status_fake_to_self(account); + } } /* }}} */ @@ -1995,7 +2014,9 @@ if (gg_ping(info->session) < 0) { purple_debug_info("gg", "Not connected to the server " "or gg_session is not correct\n"); - purple_connection_error(gc, _("Not connected to the server.")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Not connected to the server.")); } } /* }}} */ @@ -2148,7 +2169,7 @@ "prpl-gg", /* id */ "Gadu-Gadu", /* name */ - VERSION, /* version */ + DISPLAY_VERSION, /* version */ N_("Gadu-Gadu Protocol Plugin"), /* summary */ N_("Polish popular IM"), /* description */
--- a/libpurple/protocols/irc/dcc_send.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/irc/dcc_send.c Sat Nov 17 02:20:01 2007 +0000 @@ -251,7 +251,7 @@ * to the nonblocking nature of the listening socket, so we'll * just try again next time */ /* Let's print an error message anyway */ - purple_debug_warning("irc", "accept: %s\n", strerror(errno)); + purple_debug_warning("irc", "accept: %s\n", g_strerror(errno)); return; }
--- a/libpurple/protocols/irc/irc.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/irc/irc.c Sat Nov 17 02:20:01 2007 +0000 @@ -123,8 +123,10 @@ if (ret < 0 && errno == EAGAIN) return; else if (ret <= 0) { - purple_connection_error(purple_account_get_connection(irc->account), - _("Server has disconnected")); + PurpleConnection *gc = purple_account_get_connection(irc->account); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Server has disconnected")); return; } @@ -161,8 +163,10 @@ /* purple_debug(PURPLE_DEBUG_MISC, "irc", "sent%s: %s", irc->gsc ? " (ssl)" : "", tosend); */ if (ret <= 0 && errno != EAGAIN) { - purple_connection_error(purple_account_get_connection(irc->account), - _("Server has disconnected")); + PurpleConnection *gc = purple_account_get_connection(irc->account); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Server has disconnected")); } else if (ret < buflen) { if (ret < 0) ret = 0; @@ -295,7 +299,9 @@ gc->flags |= PURPLE_CONNECTION_NO_NEWLINES; if (strpbrk(username, " \t\v\r\n") != NULL) { - purple_connection_error(gc, _("IRC nicks may not contain whitespace")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_INVALID_SETTINGS, + _("IRC nicks may not contain whitespace")); return; } @@ -324,7 +330,9 @@ purple_account_get_int(account, "port", IRC_DEFAULT_SSL_PORT), irc_login_cb_ssl, irc_ssl_connect_failure, gc); } else { - purple_connection_error(gc, _("SSL support unavailable")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, + _("SSL support unavailable")); return; } } @@ -335,7 +343,9 @@ purple_account_get_int(account, "port", IRC_DEFAULT_PORT), irc_login_cb, gc) == NULL) { - purple_connection_error(gc, _("Couldn't create socket")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Couldn't create socket")); return; } } @@ -352,7 +362,6 @@ if (pass && *pass) { buf = irc_format(irc, "vv", "PASS", pass); if (irc_send(irc, buf) < 0) { -/* purple_connection_error(gc, "Error sending password"); */ g_free(buf); return FALSE; } @@ -384,14 +393,12 @@ strlen(realname) ? realname : IRC_DEFAULT_ALIAS); g_free(tmp); if (irc_send(irc, buf) < 0) { -/* purple_connection_error(gc, "Error registering with server");*/ g_free(buf); return FALSE; } g_free(buf); buf = irc_format(irc, "vn", "NICK", purple_connection_get_display_name(gc)); if (irc_send(irc, buf) < 0) { -/* purple_connection_error(gc, "Error sending nickname");*/ g_free(buf); return FALSE; } @@ -418,7 +425,9 @@ struct irc_conn *irc = gc->proto_data; if (source < 0) { - purple_connection_error(gc, _("Couldn't connect to host")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Couldn't connect to host")); return; } @@ -438,7 +447,7 @@ irc->gsc = NULL; - purple_connection_error(gc, purple_ssl_strerror(error)); + purple_connection_ssl_error (gc, error); } static void irc_close(PurpleConnection *gc) @@ -606,10 +615,14 @@ /* Try again later */ return; } else if (len < 0) { - purple_connection_error(gc, _("Read error")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Read error")); return; } else if (len == 0) { - purple_connection_error(gc, _("Server has disconnected")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Server has disconnected")); return; } @@ -631,10 +644,14 @@ if (len < 0 && errno == EAGAIN) { return; } else if (len < 0) { - purple_connection_error(gc, _("Read error")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Read error")); return; } else if (len == 0) { - purple_connection_error(gc, _("Server has disconnected")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Server has disconnected")); return; } @@ -910,7 +927,7 @@ "prpl-irc", /**< id */ "IRC", /**< name */ - VERSION, /**< version */ + DISPLAY_VERSION, /**< version */ N_("IRC Protocol Plugin"), /** summary */ N_("The IRC Protocol Plugin that Sucks Less"), /** description */ NULL, /**< author */
--- a/libpurple/protocols/irc/irc.h Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/irc/irc.h Sat Nov 17 02:20:01 2007 +0000 @@ -99,6 +99,8 @@ int irc_send(struct irc_conn *irc, const char *buf); gboolean irc_blist_timeout(struct irc_conn *irc); +char *irc_escape_privmsg(const char *text, gssize length); + char *irc_mirc2html(const char *string); char *irc_mirc2txt(const char *string);
--- a/libpurple/protocols/irc/msgs.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/irc/msgs.c Sat Nov 17 02:20:01 2007 +0000 @@ -913,9 +913,9 @@ _("Your selected nickname was rejected by the server. It probably contains invalid characters.")); } else { - gc->wants_to_die = TRUE; - purple_connection_error(purple_account_get_connection(irc->account), - _("Your selected account name was rejected by the server. It probably contains invalid characters.")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_INVALID_SETTINGS, + _("Your selected account name was rejected by the server. It probably contains invalid characters.")); } } @@ -1066,7 +1066,7 @@ return; } - msg = g_markup_escape_text(tmp, -1); + msg = irc_escape_privmsg(tmp, -1); g_free(tmp); tmp = irc_mirc2html(msg);
--- a/libpurple/protocols/irc/parse.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/irc/parse.c Sat Nov 17 02:20:01 2007 +0000 @@ -281,6 +281,61 @@ return purple_utf8_salvage(string); } +/* This function is shamelessly stolen from glib--it is an old version of the + * private function append_escaped_text, used by g_markup_escape_text, whose + * behavior changed in glib 2.12. */ +static void irc_append_escaped_text(GString *str, const char *text, gssize length) +{ + const char *p = text; + const char *end = text + length; + const char *next = NULL; + + while(p != end) { + next = g_utf8_next_char(p); + + switch(*p) { + case '&': + g_string_append(str, "&"); + break; + case '<': + g_string_append(str, "<"); + break; + case '>': + g_string_append(str, ">"); + break; + case '\'': + g_string_append(str, "'"); + break; + case '"': + g_string_append(str, """); + break; + default: + g_string_append_len(str, p, next - p); + break; + } + + p = next; + } +} + +/* This function is shamelessly stolen from glib--it is an old version of the + * function g_markup_escape_text, whose behavior changed in glib 2.12. */ +char *irc_escape_privmsg(const char *text, gssize length) +{ + GString *str; + + g_return_val_if_fail(text != NULL, NULL); + + if(length < 0) + length = strlen(text); + + str = g_string_sized_new(length); + + irc_append_escaped_text(str, text, length); + + return g_string_free(str, FALSE); +} + /* XXX tag closings are not necessarily correctly nested here! If we * get a ^O or reach the end of the string and there are open * tags, they are closed in a fixed order ... this means, for @@ -557,6 +612,7 @@ struct _irc_msg *msgent; char *cur, *end, *tmp, *from, *msgname, *fmt, **args, *msg; guint i; + PurpleConnection *gc = purple_account_get_connection(irc->account); irc->recv_time = time(NULL); @@ -565,7 +621,7 @@ * TODO: It should be passed as an array of bytes and a length * instead of a null terminated string. */ - purple_signal_emit(_irc_plugin, "irc-receiving-text", purple_account_get_connection(irc->account), &input); + purple_signal_emit(_irc_plugin, "irc-receiving-text", gc, &input); if (!strncmp(input, "PING ", 5)) { msg = irc_format(irc, "vv", "PONG", input + 5); @@ -575,10 +631,13 @@ } else if (!strncmp(input, "ERROR ", 6)) { if (g_utf8_validate(input, -1, NULL)) { char *tmp = g_strdup_printf("%s\n%s", _("Disconnected."), input); - purple_connection_error(purple_account_get_connection(irc->account), tmp); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp); g_free(tmp); } else - purple_connection_error(purple_account_get_connection(irc->account), _("Disconnected.")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Disconnected.")); return; }
--- a/libpurple/protocols/jabber/Makefile.mingw Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/jabber/Makefile.mingw Sat Nov 17 02:20:01 2007 +0000 @@ -88,6 +88,21 @@ INCLUDE_PATHS += -I$(CYRUS_SASL_TOP)/include LIB_PATHS += -L$(CYRUS_SASL_TOP)/lib LIBS += -llibsasl +CYRUS_SASL_DLLS = \ + $(CYRUS_SASL_TOP)/bin/comerr32.dll \ + $(CYRUS_SASL_TOP)/bin/gssapi32.dll \ + $(CYRUS_SASL_TOP)/bin/k5sprt32.dll \ + $(CYRUS_SASL_TOP)/bin/krb5_32.dll \ + $(CYRUS_SASL_TOP)/bin/libsasl.dll + +CYRUS_SASL_PLUGINS = \ + $(CYRUS_SASL_TOP)/bin/sasl2/saslANONYMOUS.dll \ + $(CYRUS_SASL_TOP)/bin/sasl2/saslCRAMMD5.dll \ + $(CYRUS_SASL_TOP)/bin/sasl2/saslDIGESTMD5.dll \ + $(CYRUS_SASL_TOP)/bin/sasl2/saslGSSAPI.dll \ + $(CYRUS_SASL_TOP)/bin/sasl2/saslLOGIN.dll \ + $(CYRUS_SASL_TOP)/bin/sasl2/saslPLAIN.dll + endif include $(PIDGIN_COMMON_RULES) @@ -102,6 +117,11 @@ install: all $(DLL_INSTALL_DIR) cp $(XMPP_TARGET).dll $(DLL_INSTALL_DIR) cp $(TARGET).dll $(PURPLE_INSTALL_DIR) +ifeq ($(CYRUS_SASL), 1) + mkdir -p $(PURPLE_INSTALL_DIR)/sasl2 + cp $(CYRUS_SASL_DLLS) $(PURPLE_INSTALL_DIR) + cp $(CYRUS_SASL_PLUGINS) $(PURPLE_INSTALL_DIR)/sasl2 +endif $(OBJECTS): $(PURPLE_CONFIG_H)
--- a/libpurple/protocols/jabber/adhoccommands.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/jabber/adhoccommands.c Sat Nov 17 02:20:01 2007 +0000 @@ -109,9 +109,15 @@ 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); + + /* cancel is handled differently on ad-hoc commands than regular forms */ + if(!strcmp(xmlnode_get_namespace(result),"jabber:x:data") && !strcmp(xmlnode_get_attrib(result, "type"),"cancel")) { + xmlnode_set_attrib(command,"action","cancel"); + } else { + 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; @@ -132,7 +138,7 @@ const char *type = xmlnode_get_attrib(packet,"type"); if(type && !strcmp(type,"error")) { - char *msg = jabber_parse_error(js, packet); + char *msg = jabber_parse_error(js, packet, NULL); if(!msg) msg = g_strdup(_("Unknown Error"));
--- a/libpurple/protocols/jabber/auth.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/jabber/auth.c Sat Nov 17 02:20:01 2007 +0000 @@ -50,7 +50,9 @@ "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>", -1); return TRUE; } else if(xmlnode_get_child(starttls, "required")) { - purple_connection_error(js->gc, _("Server requires TLS/SSL for login. No TLS/SSL support found.")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, + _("Server requires TLS/SSL for login. No TLS/SSL support found.")); return TRUE; } } @@ -113,7 +115,9 @@ static void disallow_plaintext_auth(PurpleAccount *account) { - purple_connection_error(account->gc, _("Server requires plaintext authentication over an unencrypted stream")); + purple_connection_error_reason (account->gc, + PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR, + _("Server requires plaintext authentication over an unencrypted stream")); } #ifdef HAVE_CYRUS_SASL @@ -331,7 +335,9 @@ * error here. */ } else { - purple_connection_error(js->gc, _("Server does not use any supported authentication method")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, + _("Server does not use any supported authentication method")); return; } /* not reached */ @@ -386,7 +392,9 @@ jabber_send(js, auth); xmlnode_free(auth); } else { - purple_connection_error(js->gc, "SASL authentication failed\n"); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, + "SASL authentication failed\n"); } } @@ -459,7 +467,9 @@ mechs = xmlnode_get_child(packet, "mechanisms"); if(!mechs) { - purple_connection_error(js->gc, _("Invalid response from server.")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Invalid response from server.")); return; } @@ -519,7 +529,8 @@ } finish_plaintext_authentication(js); } else { - purple_connection_error(js->gc, + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, _("Server does not use any supported authentication method")); } #endif @@ -532,20 +543,22 @@ if(type && !strcmp(type, "result")) { jabber_stream_set_state(js, JABBER_STREAM_CONNECTED); } else { - char *msg = jabber_parse_error(js, packet); + PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; + char *msg = jabber_parse_error(js, packet, &reason); xmlnode *error; const char *err_code; + /* FIXME: Why is this not in jabber_parse_error? */ if((error = xmlnode_get_child(packet, "error")) && (err_code = xmlnode_get_attrib(error, "code")) && !strcmp(err_code, "401")) { - js->gc->wants_to_die = TRUE; + reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED; /* Clear the pasword if it isn't being saved */ if (!purple_account_get_remember_password(js->gc->account)) purple_account_set_password(js->gc->account, NULL); } - purple_connection_error(js->gc, msg); + purple_connection_error_reason (js->gc, reason, msg); g_free(msg); } } @@ -558,11 +571,14 @@ const char *pw = purple_connection_get_password(js->gc); if(!type) { - purple_connection_error(js->gc, _("Invalid response from server.")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Invalid response from server.")); return; } else if(!strcmp(type, "error")) { - char *msg = jabber_parse_error(js, packet); - purple_connection_error(js->gc, msg); + PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; + char *msg = jabber_parse_error(js, packet, &reason); + purple_connection_error_reason (js->gc, reason, msg); g_free(msg); } else if(!strcmp(type, "result")) { query = xmlnode_get_child(packet, "query"); @@ -606,8 +622,9 @@ } finish_plaintext_authentication(js); } else { - purple_connection_error(js->gc, - _("Server does not use any supported authentication method")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, + _("Server does not use any supported authentication method")); return; } } @@ -773,7 +790,9 @@ GHashTable *parts; if(!enc_in) { - purple_connection_error(js->gc, _("Invalid response from server.")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Invalid response from server.")); return; } @@ -794,7 +813,9 @@ "<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", -1); } else { - purple_connection_error(js->gc, _("Invalid challenge from server")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Invalid challenge from server")); } g_free(js->expected_rspauth); } else { @@ -817,7 +838,9 @@ realm = js->user->domain; if (nonce == NULL || realm == NULL) - purple_connection_error(js->gc, _("Invalid challenge from server")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Invalid challenge from server")); else { GString *response = g_string_new(""); char *a2; @@ -889,7 +912,9 @@ g_free(dec_in); if (js->sasl_state != SASL_CONTINUE && js->sasl_state != SASL_OK) { purple_debug_error("jabber", "Error is %d : %s\n",js->sasl_state,sasl_errdetail(js->sasl)); - purple_connection_error(js->gc, _("SASL error")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("SASL error")); return; } else { response = xmlnode_new("response"); @@ -914,7 +939,9 @@ #endif if(!ns || strcmp(ns, "urn:ietf:params:xml:ns:xmpp-sasl")) { - purple_connection_error(js->gc, _("Invalid response from server.")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Invalid response from server.")); return; } @@ -939,7 +966,9 @@ if (js->sasl_state != SASL_OK) { /* This should never happen! */ - purple_connection_error(js->gc, _("Invalid response from server.")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Invalid response from server.")); } } /* If we've negotiated a security layer, we need to enable it */ @@ -955,12 +984,15 @@ void jabber_auth_handle_failure(JabberStream *js, xmlnode *packet) { - char *msg = jabber_parse_error(js, packet); + PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; + char *msg = jabber_parse_error(js, packet, &reason); if(!msg) { - purple_connection_error(js->gc, _("Invalid response from server.")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Invalid response from server.")); } else { - purple_connection_error(js->gc, msg); + purple_connection_error_reason (js->gc, reason, msg); g_free(msg); } }
--- a/libpurple/protocols/jabber/buddy.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/jabber/buddy.c Sat Nov 17 02:20:01 2007 +0000 @@ -279,7 +279,7 @@ char *tag; /* tag text */ char *ptag; /* parent tag "path" text */ char *url; /* vCard display format if URL */ -} vcard_template_data[] = { +} const vcard_template_data[] = { {N_("Full Name"), NULL, TRUE, TRUE, "FN", NULL, NULL}, {N_("Family Name"), NULL, TRUE, TRUE, "FAMILY", "N", NULL}, {N_("Given Name"), NULL, TRUE, TRUE, "GIVEN", "N", NULL}, @@ -311,7 +311,7 @@ struct tag_attr { char *attr; char *value; -} vcard_tag_attr_list[] = { +} const vcard_tag_attr_list[] = { {"prodid", "-//HandGen//NONSGML vGen v1.0//EN"}, {"version", "2.0", }, {"xmlns", "vcard-temp", }, @@ -337,7 +337,7 @@ * from the vCard template struct. */ if(parent_tag == NULL) { - struct vcard_template *vc_tp = vcard_template_data; + const struct vcard_template *vc_tp = vcard_template_data; while(vc_tp->label != NULL) { if(strcmp(vc_tp->tag, new_tag) == 0) { @@ -395,7 +395,7 @@ JabberIq *iq; JabberStream *js = gc->proto_data; xmlnode *vc_node; - struct tag_attr *tag_attr; + const struct tag_attr *tag_attr; /* if we have't grabbed the remote vcard yet, we can't * assume that what we have here is correct */ @@ -614,7 +614,7 @@ const char *text; char *p; const struct vcard_template *vc_tp; - struct tag_attr *tag_attr; + const struct tag_attr *tag_attr; vc_node = xmlnode_new("vCard"); @@ -2252,6 +2252,16 @@ xmlnode *query; JabberIq *iq; char *dir_server = data; + const char *type; + + /* if they've cancelled the search, we're + * just going to get an error if we send + * a cancel, so skip it */ + type = xmlnode_get_attrib(result, "type"); + if(type && !strcmp(type, "cancel")) { + g_free(dir_server); + return; + } iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:search"); query = xmlnode_get_child(iq->node, "query"); @@ -2334,7 +2344,7 @@ return; if(!(type = xmlnode_get_attrib(packet, "type")) || !strcmp(type, "error")) { - char *msg = jabber_parse_error(js, packet); + char *msg = jabber_parse_error(js, packet, NULL); if(!msg) msg = g_strdup(_("Unknown error"));
--- a/libpurple/protocols/jabber/chat.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/jabber/chat.c Sat Nov 17 02:20:01 2007 +0000 @@ -391,7 +391,7 @@ } } } else if(!strcmp(type, "error")) { - char *msg = jabber_parse_error(js, packet); + char *msg = jabber_parse_error(js, packet, NULL); purple_notify_error(js->gc, _("Configuration error"), _("Configuration error"), msg); @@ -465,7 +465,7 @@ const char *type = xmlnode_get_attrib(packet, "type"); if(type && !strcmp(type, "error")) { - char *msg = jabber_parse_error(js, packet); + char *msg = jabber_parse_error(js, packet, NULL); purple_notify_error(js->gc, _("Registration error"), _("Registration error"), msg); @@ -534,7 +534,7 @@ } } } else if(!strcmp(type, "error")) { - char *msg = jabber_parse_error(js, packet); + char *msg = jabber_parse_error(js, packet, NULL); purple_notify_error(js->gc, _("Registration error"), _("Registration error"), msg); @@ -673,7 +673,7 @@ return; if(!(type = xmlnode_get_attrib(packet, "type")) || strcmp(type, "result")) { - char *err = jabber_parse_error(js,packet); + char *err = jabber_parse_error(js, packet, NULL); purple_notify_error(js->gc, _("Error"), _("Error retrieving room list"), err); purple_roomlist_set_in_progress(js->roomlist, FALSE); @@ -684,7 +684,7 @@ } if(!(query = xmlnode_get_child(packet, "query"))) { - char *err = jabber_parse_error(js, packet); + char *err = jabber_parse_error(js, packet, NULL); purple_notify_error(js->gc, _("Error"), _("Error retrieving room list"), err); purple_roomlist_set_in_progress(js->roomlist, FALSE); @@ -964,7 +964,7 @@ static void jabber_chat_disco_traffic_cb(JabberStream *js, xmlnode *packet, gpointer data) { JabberChat *chat; - xmlnode *query; + /*xmlnode *query;*/ int id = GPOINTER_TO_INT(data); if(!(chat = jabber_chat_find_by_id(js, id))) @@ -974,6 +974,8 @@ * support this request */ chat->xhtml = TRUE; + /* disabling this until more MUC servers support + * announcing this if(xmlnode_get_child(packet, "error")) { return; } @@ -981,8 +983,6 @@ if(!(query = xmlnode_get_child(packet, "query"))) return; - /* disabling this until more MUC servers support - * announcing this chat->xhtml = FALSE; for(x = xmlnode_get_child(query, "feature"); x; x = xmlnode_get_next_twin(x)) {
--- a/libpurple/protocols/jabber/google.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/jabber/google.c Sat Nov 17 02:20:01 2007 +0000 @@ -110,7 +110,7 @@ tos[i] = (to_name != NULL ? to_name : ""); froms[i] = (from != NULL ? from : ""); subjects[i] = (subject != NULL ? subject : g_strdup("")); - urls[i] = (url != NULL ? url : ""); + urls[i] = url; tid = xmlnode_get_attrib(message, "tid"); if (tid && @@ -231,7 +231,7 @@ const char *jid = xmlnode_get_attrib(item, "jid"); gboolean on_block_list = FALSE; - char *jid_norm = g_strdup(jabber_normalize(account, jid)); + char *jid_norm; const char *grt = xmlnode_get_attrib_with_namespace(item, "t", "google:roster"); const char *subscription = xmlnode_get_attrib(item, "subscription"); @@ -243,6 +243,8 @@ return FALSE; } + jid_norm = g_strdup(jabber_normalize(account, jid)); + while (list) { if (!strcmp(jid_norm, (char*)list->data)) { on_block_list = TRUE; @@ -514,3 +516,22 @@ } return g_string_free(str, FALSE); } + +void jabber_google_presence_incoming(JabberStream *js, const char *user, JabberBuddyResource *jbr) +{ + if (!js->googletalk) + return; + if (jbr->status && !strncmp(jbr->status, "♫ ", strlen("♫ "))) { + purple_prpl_got_user_status(js->gc->account, user, "tune", + PURPLE_TUNE_TITLE, jbr->status + strlen("♫ "), NULL); + jbr->status = NULL; + } else { + purple_prpl_got_user_status_deactive(js->gc->account, user, "tune"); + } +} + +char *jabber_google_presence_outgoing(PurpleStatus *tune) +{ + char *ret = g_strdup_printf("♫ %s", purple_status_get_attr_string(tune, PURPLE_TUNE_TITLE)); + return ret; +}
--- a/libpurple/protocols/jabber/google.h Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/jabber/google.h Sat Nov 17 02:20:01 2007 +0000 @@ -36,6 +36,10 @@ * if this roster item should continue to be processed */ gboolean jabber_google_roster_incoming(JabberStream *js, xmlnode *item); + +void jabber_google_presence_incoming(JabberStream *js, const char *who, JabberBuddyResource *jbr); +char *jabber_google_presence_outgoing(PurpleStatus *tune); + void jabber_google_roster_add_deny(PurpleConnection *gc, const char *who); void jabber_google_roster_rem_deny(PurpleConnection *gc, const char *who);
--- a/libpurple/protocols/jabber/iq.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/jabber/iq.c Sat Nov 17 02:20:01 2007 +0000 @@ -248,7 +248,6 @@ JabberIq *iq; const char *type, *from, *id; xmlnode *query; - char *os = NULL; type = xmlnode_get_attrib(packet, "type"); @@ -256,6 +255,7 @@ GHashTable *ui_info; const char *ui_name = NULL, *ui_version = NULL; #if 0 + char *os = NULL; if(!purple_prefs_get_bool("/plugins/prpl/jabber/hide_os")) { struct utsname osinfo; @@ -290,10 +290,12 @@ xmlnode_insert_data(xmlnode_new_child(query, "version"), VERSION, -1); } +#if 0 if(os) { xmlnode_insert_data(xmlnode_new_child(query, "os"), os, -1); g_free(os); } +#endif jabber_iq_send(iq); }
--- a/libpurple/protocols/jabber/jabber.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/jabber/jabber.c Sat Nov 17 02:20:01 2007 +0000 @@ -60,7 +60,7 @@ #define JABBER_CONNECT_STEPS (js->gsc ? 9 : 5) static PurplePlugin *my_protocol = NULL; -GList *jabber_features; +GList *jabber_features = NULL; static void jabber_unregister_account_cb(JabberStream *js); @@ -89,7 +89,9 @@ if(js->unregistration) jabber_unregister_account_cb(js); } else { - purple_connection_error(js->gc, _("Error initializing session")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + ("Error initializing session")); } } @@ -120,15 +122,18 @@ JabberBuddy *my_jb = NULL; jabber_id_free(js->user); if(!(js->user = jabber_id_new(full_jid))) { - purple_connection_error(js->gc, _("Invalid response from server.")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Invalid response from server.")); } if((my_jb = jabber_buddy_find(js, full_jid, TRUE))) my_jb->subscription |= JABBER_SUB_BOTH; g_free(full_jid); } } else { - char *msg = jabber_parse_error(js, packet); - purple_connection_error(js->gc, msg); + PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; + char *msg = jabber_parse_error(js, packet, &reason); + purple_connection_error_reason (js->gc, reason, msg); g_free(msg); } @@ -141,8 +146,9 @@ if(jabber_process_starttls(js, packet)) return; } else if(purple_account_get_bool(js->gc->account, "require_tls", FALSE) && !js->gsc) { - js->gc->wants_to_die = TRUE; - purple_connection_error(js->gc, _("You require encryption, but it is not available on this server.")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR, + _("You require encryption, but it is not available on this server.")); return; } @@ -173,9 +179,11 @@ static void jabber_stream_handle_error(JabberStream *js, xmlnode *packet) { - char *msg = jabber_parse_error(js, packet); + PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; + char *msg = jabber_parse_error(js, packet, &reason); - purple_connection_error(js->gc, msg); + purple_connection_error_reason (js->gc, reason, msg); + g_free(msg); } @@ -201,11 +209,11 @@ jabber_message_parse(js, *packet); } else if(!strcmp((*packet)->name, "stream:features")) { jabber_stream_features_parse(js, *packet); - } else if (!strcmp((*packet)->name, "features") && + } else if (!strcmp((*packet)->name, "features") && xmlns && !strcmp(xmlns, "http://etherx.jabber.org/streams")) { jabber_stream_features_parse(js, *packet); } else if(!strcmp((*packet)->name, "stream:error") || - (!strcmp((*packet)->name, "error") && + (!strcmp((*packet)->name, "error") && xmlns && !strcmp(xmlns, "http://etherx.jabber.org/streams"))) { jabber_stream_handle_error(js, *packet); @@ -256,7 +264,9 @@ if (ret < 0 && errno == EAGAIN) return; else if (ret <= 0) { - purple_connection_error(js->gc, _("Write error")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Write error")); return; } @@ -309,7 +319,9 @@ } if (ret < 0 && errno != EAGAIN) - purple_connection_error(js->gc, _("Write error")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Write error")); else if (ret < olen) { if (ret < 0) ret = 0; @@ -337,7 +349,9 @@ } if (ret < 0 && errno != EAGAIN) - purple_connection_error(js->gc, _("Write error")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Write error")); else if (ret < len) { if (ret < 0) ret = 0; @@ -405,7 +419,9 @@ if(errno == EAGAIN) return; else - purple_connection_error(gc, _("Read Error")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Read Error")); } static void @@ -442,7 +458,9 @@ } else if(errno == EAGAIN) { return; } else { - purple_connection_error(gc, _("Read Error")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Read Error")); } } @@ -481,7 +499,8 @@ gchar *tmp; tmp = g_strdup_printf(_("Could not establish a connection with the server:\n%s"), error); - purple_connection_error(gc, tmp); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp); g_free(tmp); return; } @@ -509,7 +528,7 @@ js = gc->proto_data; js->gsc = NULL; - purple_connection_error(gc, purple_ssl_strerror(error)); + purple_connection_ssl_error (gc, error); } static void tls_init(JabberStream *js) @@ -526,7 +545,9 @@ if (purple_proxy_connect(js->gc, js->gc->account, host, port, jabber_login_callback, js->gc) == NULL) - purple_connection_error(js->gc, _("Unable to create socket")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Unable to create socket")); } static void srv_resolved_cb(PurpleSrvResponse *resp, int results, gpointer data) @@ -572,12 +593,16 @@ js->old_length = -1; if(!js->user) { - purple_connection_error(gc, _("Invalid XMPP ID")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_INVALID_SETTINGS, + _("Invalid XMPP ID")); return; } if (!js->user->domain || *(js->user->domain) == '\0') { - purple_connection_error(gc, _("Invalid XMPP ID. Domain must be set.")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_INVALID_SETTINGS, + _("Invalid XMPP ID. Domain must be set.")); return; } @@ -607,7 +632,9 @@ purple_account_get_int(account, "port", 5223), jabber_login_callback_ssl, jabber_ssl_connect_failure, js->gc); } else { - purple_connection_error(js->gc, _("SSL support unavailable")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, + _("SSL support unavailable")); } } @@ -665,7 +692,7 @@ _("Registration Successful"), buf); g_free(buf); } else { - char *msg = jabber_parse_error(js, packet); + char *msg = jabber_parse_error(js, packet, NULL); if(!msg) msg = g_strdup(_("Unknown Error")); @@ -695,7 +722,7 @@ _("Unregistration Successful"), buf); g_free(buf); } else { - char *msg = jabber_parse_error(js, packet); + char *msg = jabber_parse_error(js, packet, NULL); if(!msg) msg = g_strdup(_("Unknown Error")); @@ -1060,7 +1087,9 @@ js->old_length = -1; if(!js->user) { - purple_connection_error(gc, _("Invalid XMPP ID")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_INVALID_SETTINGS, + _("Invalid XMPP ID")); return; } @@ -1092,7 +1121,9 @@ purple_account_get_int(account, "port", 5222), jabber_login_callback_ssl, jabber_ssl_connect_failure, gc); } else { - purple_connection_error(gc, _("SSL support unavailable")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, + _("SSL support unavailable")); } } @@ -1115,7 +1146,7 @@ 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); + char *msg = jabber_parse_error(js, packet, NULL); purple_notify_error(js->gc, _("Error unregistering account"), _("Error unregistering account"), msg); @@ -1353,7 +1384,7 @@ g_free(feat->namespace); g_free(feature->data); - feature = g_list_delete_link(feature, feature); + jabber_features = g_list_delete_link(jabber_features, feature); break; } } @@ -1400,10 +1431,11 @@ char *stripped; if(!(stripped = purple_markup_strip_html(jabber_buddy_get_status_msg(jb)))) { - PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(b)); - - if(!purple_status_is_available(status)) - stripped = g_strdup(purple_status_get_name(status)); + PurplePresence *presence = purple_buddy_get_presence(b); + if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_TUNE)) { + PurpleStatus *status = purple_presence_get_status(presence, "tune"); + stripped = g_strdup(purple_status_get_attr_string(status, PURPLE_TUNE_TITLE)); + } } if(stripped) { @@ -1429,6 +1461,7 @@ if(jb) { JabberBuddyResource *jbr = NULL; + PurplePresence *presence = purple_buddy_get_presence(b); const char *sub; GList *l; const char *mood; @@ -1455,7 +1488,7 @@ purple_notify_user_info_add_pair(user_info, _("Subscription"), sub); - status = purple_presence_get_active_status(purple_buddy_get_presence(b)); + status = purple_presence_get_active_status(presence); value = purple_status_get_attr_value(status, "mood"); if (value && purple_value_get_type(value) == PURPLE_TYPE_STRING && (mood = purple_value_get_string(value))) { @@ -1467,7 +1500,12 @@ g_free(moodplustext); } else purple_notify_user_info_add_pair(user_info, _("Mood"), mood); - } + } + if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_TUNE)) { + PurpleStatus *tune = purple_presence_get_status(presence, "tune"); + const char *title = purple_status_get_attr_string(tune, PURPLE_TUNE_TITLE); + purple_notify_user_info_add_pair(user_info, _("Current media"), title); + } } for(l=jb->resources; l; l = l->next) { @@ -1532,15 +1570,6 @@ "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), - PURPLE_TUNE_ARTIST, _("Tune Artist"), purple_value_new(PURPLE_TYPE_STRING), - PURPLE_TUNE_TITLE, _("Tune Title"), purple_value_new(PURPLE_TYPE_STRING), - PURPLE_TUNE_ALBUM, _("Tune Album"), purple_value_new(PURPLE_TYPE_STRING), - PURPLE_TUNE_GENRE, _("Tune Genre"), purple_value_new(PURPLE_TYPE_STRING), - PURPLE_TUNE_COMMENT, _("Tune Comment"), purple_value_new(PURPLE_TYPE_STRING), - PURPLE_TUNE_TRACK, _("Tune Track"), purple_value_new(PURPLE_TYPE_STRING), - PURPLE_TUNE_TIME, _("Tune Time"), purple_value_new(PURPLE_TYPE_INT), - PURPLE_TUNE_YEAR, _("Tune Year"), purple_value_new(PURPLE_TYPE_INT), - PURPLE_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); @@ -1555,15 +1584,6 @@ "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), - PURPLE_TUNE_ARTIST, _("Tune Artist"), purple_value_new(PURPLE_TYPE_STRING), - PURPLE_TUNE_TITLE, _("Tune Title"), purple_value_new(PURPLE_TYPE_STRING), - PURPLE_TUNE_ALBUM, _("Tune Album"), purple_value_new(PURPLE_TYPE_STRING), - PURPLE_TUNE_GENRE, _("Tune Genre"), purple_value_new(PURPLE_TYPE_STRING), - PURPLE_TUNE_COMMENT, _("Tune Comment"), purple_value_new(PURPLE_TYPE_STRING), - PURPLE_TUNE_TRACK, _("Tune Track"), purple_value_new(PURPLE_TYPE_STRING), - PURPLE_TUNE_TIME, _("Tune Time"), purple_value_new(PURPLE_TYPE_INT), - PURPLE_TUNE_YEAR, _("Tune Year"), purple_value_new(PURPLE_TYPE_INT), - PURPLE_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); @@ -1578,15 +1598,6 @@ "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), - PURPLE_TUNE_ARTIST, _("Tune Artist"), purple_value_new(PURPLE_TYPE_STRING), - PURPLE_TUNE_TITLE, _("Tune Title"), purple_value_new(PURPLE_TYPE_STRING), - PURPLE_TUNE_ALBUM, _("Tune Album"), purple_value_new(PURPLE_TYPE_STRING), - PURPLE_TUNE_GENRE, _("Tune Genre"), purple_value_new(PURPLE_TYPE_STRING), - PURPLE_TUNE_COMMENT, _("Tune Comment"), purple_value_new(PURPLE_TYPE_STRING), - PURPLE_TUNE_TRACK, _("Tune Track"), purple_value_new(PURPLE_TYPE_STRING), - PURPLE_TUNE_TIME, _("Tune Time"), purple_value_new(PURPLE_TYPE_INT), - PURPLE_TUNE_YEAR, _("Tune Year"), purple_value_new(PURPLE_TYPE_INT), - PURPLE_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); @@ -1601,15 +1612,6 @@ "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), - PURPLE_TUNE_ARTIST, _("Tune Artist"), purple_value_new(PURPLE_TYPE_STRING), - PURPLE_TUNE_TITLE, _("Tune Title"), purple_value_new(PURPLE_TYPE_STRING), - PURPLE_TUNE_ALBUM, _("Tune Album"), purple_value_new(PURPLE_TYPE_STRING), - PURPLE_TUNE_GENRE, _("Tune Genre"), purple_value_new(PURPLE_TYPE_STRING), - PURPLE_TUNE_COMMENT, _("Tune Comment"), purple_value_new(PURPLE_TYPE_STRING), - PURPLE_TUNE_TRACK, _("Tune Track"), purple_value_new(PURPLE_TYPE_STRING), - PURPLE_TUNE_TIME, _("Tune Time"), purple_value_new(PURPLE_TYPE_INT), - PURPLE_TUNE_YEAR, _("Tune Year"), purple_value_new(PURPLE_TYPE_INT), - PURPLE_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); @@ -1624,15 +1626,6 @@ "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), - PURPLE_TUNE_ARTIST, _("Tune Artist"), purple_value_new(PURPLE_TYPE_STRING), - PURPLE_TUNE_TITLE, _("Tune Title"), purple_value_new(PURPLE_TYPE_STRING), - PURPLE_TUNE_ALBUM, _("Tune Album"), purple_value_new(PURPLE_TYPE_STRING), - PURPLE_TUNE_GENRE, _("Tune Genre"), purple_value_new(PURPLE_TYPE_STRING), - PURPLE_TUNE_COMMENT, _("Tune Comment"), purple_value_new(PURPLE_TYPE_STRING), - PURPLE_TUNE_TRACK, _("Tune Track"), purple_value_new(PURPLE_TYPE_STRING), - PURPLE_TUNE_TIME, _("Tune Time"), purple_value_new(PURPLE_TYPE_INT), - PURPLE_TUNE_YEAR, _("Tune Year"), purple_value_new(PURPLE_TYPE_INT), - PURPLE_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); @@ -1650,6 +1643,20 @@ NULL); types = g_list_append(types, type); + type = purple_status_type_new_with_attrs(PURPLE_STATUS_TUNE, + "tune", NULL, TRUE, TRUE, TRUE, + PURPLE_TUNE_ARTIST, _("Tune Artist"), purple_value_new(PURPLE_TYPE_STRING), + PURPLE_TUNE_TITLE, _("Tune Title"), purple_value_new(PURPLE_TYPE_STRING), + PURPLE_TUNE_ALBUM, _("Tune Album"), purple_value_new(PURPLE_TYPE_STRING), + PURPLE_TUNE_GENRE, _("Tune Genre"), purple_value_new(PURPLE_TYPE_STRING), + PURPLE_TUNE_COMMENT, _("Tune Comment"), purple_value_new(PURPLE_TYPE_STRING), + PURPLE_TUNE_TRACK, _("Tune Track"), purple_value_new(PURPLE_TYPE_STRING), + PURPLE_TUNE_TIME, _("Tune Time"), purple_value_new(PURPLE_TYPE_INT), + PURPLE_TUNE_YEAR, _("Tune Year"), purple_value_new(PURPLE_TYPE_INT), + PURPLE_TUNE_URL, _("Tune URL"), purple_value_new(PURPLE_TYPE_STRING), + NULL); + types = g_list_append(types, type); + return types; } @@ -1664,13 +1671,17 @@ if(type && !strcmp(type, "result")) { purple_notify_info(js->gc, _("Password Changed"), _("Password Changed"), _("Your password has been changed.")); + + purple_account_set_password(js->gc->account, (char *)data); } else { - char *msg = jabber_parse_error(js, packet); + char *msg = jabber_parse_error(js, packet, NULL); purple_notify_error(js->gc, _("Error changing password"), _("Error changing password"), msg); g_free(msg); } + + g_free(data); } static void jabber_password_change_cb(JabberStream *js, @@ -1699,11 +1710,9 @@ y = xmlnode_new_child(query, "password"); xmlnode_insert_data(y, p1, -1); - jabber_iq_set_callback(iq, jabber_password_change_result_cb, NULL); + jabber_iq_set_callback(iq, jabber_password_change_result_cb, g_strdup(p1)); jabber_iq_send(iq); - - purple_account_set_password(js->gc->account, p1); } static void jabber_password_change(PurplePluginAction *action) @@ -1829,13 +1838,18 @@ } -char *jabber_parse_error(JabberStream *js, xmlnode *packet) +char *jabber_parse_error(JabberStream *js, + xmlnode *packet, + PurpleConnectionError *reason) { xmlnode *error; const char *code = NULL, *text = NULL; const char *xmlns = xmlnode_get_namespace(packet); char *cdata = NULL; +#define SET_REASON(x) \ + if(reason != NULL) { *reason = x; } + if((error = xmlnode_get_child(packet, "error"))) { cdata = xmlnode_get_data(error); code = xmlnode_get_attrib(error, "code"); @@ -1887,22 +1901,21 @@ text = _("Unknown Error"); } } else if(xmlns && !strcmp(xmlns, "urn:ietf:params:xml:ns:xmpp-sasl")) { + /* Most common reason can be the default */ + SET_REASON(PURPLE_CONNECTION_ERROR_NETWORK_ERROR); if(xmlnode_get_child(packet, "aborted")) { - js->gc->wants_to_die = TRUE; text = _("Authorization Aborted"); } else if(xmlnode_get_child(packet, "incorrect-encoding")) { text = _("Incorrect encoding in authorization"); } else if(xmlnode_get_child(packet, "invalid-authzid")) { - js->gc->wants_to_die = TRUE; text = _("Invalid authzid"); } else if(xmlnode_get_child(packet, "invalid-mechanism")) { - js->gc->wants_to_die = TRUE; text = _("Invalid Authorization Mechanism"); } else if(xmlnode_get_child(packet, "mechanism-too-weak")) { - js->gc->wants_to_die = TRUE; + SET_REASON(PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE); text = _("Authorization mechanism too weak"); } else if(xmlnode_get_child(packet, "not-authorized")) { - js->gc->wants_to_die = TRUE; + SET_REASON(PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED); /* Clear the pasword if it isn't being saved */ if (!purple_account_get_remember_password(js->gc->account)) purple_account_set_password(js->gc->account, NULL); @@ -1910,18 +1923,20 @@ } else if(xmlnode_get_child(packet, "temporary-auth-failure")) { text = _("Temporary Authentication Failure"); } else { - js->gc->wants_to_die = TRUE; + SET_REASON(PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED); text = _("Authentication Failure"); } } else if(!strcmp(packet->name, "stream:error") || - (!strcmp(packet->name, "error") && + (!strcmp(packet->name, "error") && xmlns && !strcmp(xmlns, "http://etherx.jabber.org/streams"))) { + /* Most common reason as default: */ + SET_REASON(PURPLE_CONNECTION_ERROR_NETWORK_ERROR); if(xmlnode_get_child(packet, "bad-format")) { text = _("Bad Format"); } else if(xmlnode_get_child(packet, "bad-namespace-prefix")) { text = _("Bad Namespace Prefix"); } else if(xmlnode_get_child(packet, "conflict")) { - js->gc->wants_to_die = TRUE; + SET_REASON(PURPLE_CONNECTION_ERROR_NAME_IN_USE); text = _("Resource Conflict"); } else if(xmlnode_get_child(packet, "connection-timeout")) { text = _("Connection Timeout"); @@ -1970,6 +1985,8 @@ } } +#undef SET_REASON + if(text || cdata) { char *ret = g_strdup_printf("%s%s%s", code ? code : "", code ? ": " : "", text ? text : cdata); @@ -2189,55 +2206,67 @@ return PURPLE_CMD_RET_OK; } +static gboolean _jabber_send_buzz(JabberStream *js, const char *username, char **error) { + + JabberBuddy *jb; + JabberBuddyResource *jbr; + GList *iter; + + if(!username) + return FALSE; + + jb = jabber_buddy_find(js, username, FALSE); + if(!jb) { + *error = g_strdup_printf(_("Unable to buzz, because there is nothing known about user %s."), username); + return FALSE; + } + + jbr = jabber_buddy_find_resource(jb, NULL); + if(!jbr) { + *error = g_strdup_printf(_("Unable to buzz, because user %s might be offline."), username); + return FALSE; + } + + if(!jbr->caps) { + *error = g_strdup_printf(_("Unable to buzz, because there is nothing known about user %s."), username); + return FALSE; + } + + for(iter = jbr->caps->features; iter; iter = g_list_next(iter)) { + if(!strcmp(iter->data, "http://www.xmpp.org/extensions/xep-0224.html#ns")) { + xmlnode *buzz, *msg = xmlnode_new("message"); + gchar *to; + + to = g_strdup_printf("%s/%s", username, 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 TRUE; + } + } + + *error = g_strdup_printf(_("Unable to buzz, because the user %s does not support it."), username); + return FALSE; +} + 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; + + return _jabber_send_buzz(js, args[0], error) ? PURPLE_CMD_RET_OK : PURPLE_CMD_RET_FAILED; } GList *jabber_attention_types(PurpleAccount *account) @@ -2258,23 +2287,16 @@ gboolean jabber_send_attention(PurpleConnection *gc, const char *username, guint code) { - PurpleConversation *conv; - char *error; - char *args[1]; - PurpleCmdRet ret; - - conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, username, gc->account); - - args[0] = (char *)username; + JabberStream *js = gc->proto_data; + gchar *error = NULL; - ret = jabber_cmd_buzz(conv, "buzz", args, &error, NULL); - - if (ret == PURPLE_CMD_RET_FAILED) { + if (!_jabber_send_buzz(js, username, &error)) { purple_debug_error("jabber", "jabber_send_attention: jabber_cmd_buzz failed with error: %s\n", error ? error : "(NULL)"); + g_free(error); return FALSE; - } else { - return TRUE; } + + return TRUE; }
--- a/libpurple/protocols/jabber/jabber.h Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/jabber/jabber.h Sat Nov 17 02:20:01 2007 +0000 @@ -216,7 +216,14 @@ char *jabber_get_next_id(JabberStream *js); -char *jabber_parse_error(JabberStream *js, xmlnode *packet); +/** Parse an error into a human-readable string and optionally a disconnect + * reason. + * @param js the stream on which the error occurred. + * @param packet the error packet + * @param reason where to store the disconnection reason, or @c NULL if you + * don't care or you don't intend to close the connection. + */ +char *jabber_parse_error(JabberStream *js, xmlnode *packet, PurpleConnectionError *reason); void jabber_add_feature(const gchar *shortname, const gchar *namespace, JabberFeatureEnabled cb); /* cb may be NULL */ void jabber_remove_feature(const gchar *shortname);
--- a/libpurple/protocols/jabber/libxmpp.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/jabber/libxmpp.c Sat Nov 17 02:20:01 2007 +0000 @@ -165,7 +165,7 @@ "prpl-jabber", /**< id */ "XMPP", /**< name */ - VERSION, /**< version */ + DISPLAY_VERSION, /**< version */ /** summary */ N_("XMPP Protocol Plugin"), /** description */ @@ -193,6 +193,9 @@ init_plugin(PurplePlugin *plugin) { #ifdef HAVE_CYRUS_SASL +#ifdef _WIN32 + gchar *sasldir; +#endif int ret; #endif PurpleAccountUserSplit *split; @@ -237,6 +240,11 @@ /* XXX - If any other plugin wants SASL this won't be good ... */ #ifdef HAVE_CYRUS_SASL +#ifdef _WIN32 + sasldir = g_build_filename(wpurple_install_dir(), "sasl2", NULL); + sasl_set_path(SASL_PATH_TYPE_PLUGIN, sasldir); + g_free(sasldir); +#endif if ((ret = sasl_client_init(NULL)) != SASL_OK) { purple_debug_error("xmpp", "Error (%d) initializing SASL.\n", ret); }
--- a/libpurple/protocols/jabber/message.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/jabber/message.c Sat Nov 17 02:20:01 2007 +0000 @@ -104,6 +104,7 @@ g_snprintf(buf, sizeof(buf), _("%s has left the conversation."), escaped); + g_free(escaped); /* At some point when we restructure PurpleConversation, * this should be able to be implemented by removing the
--- a/libpurple/protocols/jabber/parser.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/jabber/parser.c Sat Nov 17 02:20:01 2007 +0000 @@ -80,7 +80,7 @@ char *attrib_ns = NULL; if (attributes[i+2]) { - attrib_ns = g_strdup((char*)attributes[i+2]);; + attrib_ns = g_strdup((char*)attributes[i+2]); } memcpy(attrib, attributes[i+3], attrib_len); @@ -193,7 +193,9 @@ js->context = xmlCreatePushParserCtxt(&jabber_parser_libxml, js, buf, len, NULL); xmlParseChunk(js->context, "", 0, 0); } else if (xmlParseChunk(js->context, buf, len, 0) < 0) { - purple_connection_error(js->gc, _("XML Parse error")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("XML Parse error")); } }
--- a/libpurple/protocols/jabber/presence.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/jabber/presence.c Sat Nov 17 02:20:01 2007 +0000 @@ -33,6 +33,7 @@ #include "buddy.h" #include "chat.h" +#include "google.h" #include "presence.h" #include "iq.h" #include "jutil.h" @@ -104,13 +105,14 @@ char *stripped = NULL; JabberBuddyState state; int priority; - const char *artist, *title, *source, *uri, *track; - int length; + const char *artist = NULL, *title = NULL, *source = NULL, *uri = NULL, *track = NULL; + int length = -1; gboolean allowBuzz; + PurplePresence *p = purple_account_get_presence(account); + PurpleStatus *tune; - if(NULL == status) { - PurplePresence *gpresence = purple_account_get_presence(account); - status = purple_presence_get_active_status(gpresence); + if (NULL == status) { + status = purple_presence_get_active_status(p); } if(!purple_status_is_active(status)) @@ -144,6 +146,12 @@ 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; + + if (js->googletalk && stripped == NULL && purple_presence_is_status_primitive_active(p, PURPLE_STATUS_TUNE)) { + tune = purple_presence_get_status(p, "tune"); + stripped = jabber_google_presence_outgoing(tune); + } + presence = jabber_presence_create_js(js, state, stripped, priority); if(js->avatar_hash) { @@ -172,12 +180,16 @@ } /* 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); + tune = purple_presence_get_status(p, "tune"); + if (tune && purple_status_is_active(tune)) { + artist = purple_status_get_attr_string(tune, PURPLE_TUNE_ARTIST); + title = purple_status_get_attr_string(tune, PURPLE_TUNE_TITLE); + source = purple_status_get_attr_string(tune, PURPLE_TUNE_ALBUM); + uri = purple_status_get_attr_string(tune, PURPLE_TUNE_URL); + track = purple_status_get_attr_string(tune, PURPLE_TUNE_TRACK); + length = (!purple_status_get_attr_value(tune, PURPLE_TUNE_TIME)) ? -1 : + purple_status_get_attr_int(tune, 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)) { @@ -378,7 +390,8 @@ break; } } - g_free(user_data); + g_free(userdata->from); + g_free(userdata); } void jabber_presence_parse(JabberStream *js, xmlnode *packet) @@ -416,7 +429,7 @@ } if(type && !strcmp(type, "error")) { - char *msg = jabber_parse_error(js, packet); + char *msg = jabber_parse_error(js, packet, NULL); state = JABBER_BUDDY_STATE_ERROR; jb->error_msg = msg ? msg : g_strdup(_("Unknown Error in presence")); @@ -548,7 +561,7 @@ char *room_jid = g_strdup_printf("%s@%s", jid->node, jid->domain); if(state == JABBER_BUDDY_STATE_ERROR) { - char *title, *msg = jabber_parse_error(js, packet); + char *title, *msg = jabber_parse_error(js, packet, NULL); if(chat->conv) { title = g_strdup_printf(_("Error in chat %s"), from); @@ -730,7 +743,8 @@ } if((found_jbr = jabber_buddy_find_resource(jb, NULL))) { - purple_prpl_got_user_status(js->gc->account, buddy_name, jabber_buddy_state_get_status_id(found_jbr->state), "priority", found_jbr->priority, found_jbr->status ? "message" : NULL, found_jbr->status, NULL); + jabber_google_presence_incoming(js, buddy_name, found_jbr); + purple_prpl_got_user_status(js->gc->account, buddy_name, jabber_buddy_state_get_status_id(found_jbr->state), "priority", found_jbr->priority, "message", found_jbr->status, NULL); } else { purple_prpl_got_user_status(js->gc->account, buddy_name, "offline", status ? "message" : NULL, status, NULL); }
--- a/libpurple/protocols/jabber/roster.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/jabber/roster.c Sat Nov 17 02:20:01 2007 +0000 @@ -67,8 +67,10 @@ if(!groups) { if(!buddies) g2 = g_slist_append(g2, g_strdup(_("Buddies"))); - else + else { + g_slist_free(buddies); return; + } } my_bare_jid = g_strdup_printf("%s@%s", js->user->node, js->user->domain); @@ -229,6 +231,11 @@ remove_purple_buddies(js, jid); } else { GSList *groups = NULL; + + if (js->server_caps & JABBER_CAP_GOOGLE_ROSTER) + if (!jabber_google_roster_incoming(js, item)) + continue; + for(group = xmlnode_get_child(item, "group"); group; group = xmlnode_get_next_twin(group)) { char *group_name; @@ -237,10 +244,9 @@ if (g_slist_find_custom(groups, group_name, (GCompareFunc)purple_utf8_strcasecmp) == NULL) groups = g_slist_append(groups, group_name); + else + g_free(group_name); } - if (js->server_caps & JABBER_CAP_GOOGLE_ROSTER) - if (!jabber_google_roster_incoming(js, item)) - continue; add_purple_buddies_to_groups(js, jid, name, groups); } } @@ -263,6 +269,9 @@ JabberIq *iq; xmlnode *query, *item, *group; + if(!(b = purple_find_buddy(js->gc->account, name))) + return; + if(grps) { groups = grps; } else { @@ -277,9 +286,6 @@ } } - if(!(b = purple_find_buddy(js->gc->account, name))) - return; - iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:roster"); query = xmlnode_get_child(iq->node, "query"); @@ -397,12 +403,12 @@ void jabber_roster_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group) { GSList *buddies = purple_find_buddies(gc->account, buddy->name); - GSList *groups = NULL; buddies = g_slist_remove(buddies, buddy); if(buddies != NULL) { PurpleBuddy *tmpbuddy; PurpleGroup *tmpgroup; + GSList *groups = NULL; while(buddies) { tmpbuddy = buddies->data; @@ -412,6 +418,7 @@ } jabber_roster_update(gc->proto_data, buddy->name, groups); + g_slist_free(groups); } else { JabberIq *iq = jabber_iq_new_query(gc->proto_data, JABBER_IQ_SET, "jabber:iq:roster"); @@ -423,9 +430,4 @@ jabber_iq_send(iq); } - - if(buddies) - g_slist_free(buddies); - if(groups) - g_slist_free(groups); }
--- a/libpurple/protocols/jabber/si.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/jabber/si.c Sat Nov 17 02:20:01 2007 +0000 @@ -536,7 +536,7 @@ if(acceptfd == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) return; else if(acceptfd == -1) { - purple_debug_warning("jabber", "accept: %s\n", strerror(errno)); + purple_debug_warning("jabber", "accept: %s\n", g_strerror(errno)); return; } @@ -824,6 +824,7 @@ do_transfer_send(xfer, resource); g_free(resource); + return; } jb = jabber_buddy_find(jsx->js, xfer->who, TRUE);
--- a/libpurple/protocols/jabber/usermood.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/jabber/usermood.c Sat Nov 17 02:20:01 2007 +0000 @@ -26,8 +26,9 @@ #include <string.h> #include "internal.h" #include "request.h" +#include "debug.h" -static const char *moodstrings[] = { +static const char * const moodstrings[] = { "afraid", "amazed", "angry", @@ -145,9 +146,26 @@ } 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")); + JabberStream *js; + int max_mood_idx; + int selected_mood = purple_request_fields_get_choice(fields, "mood"); + + if (!PURPLE_CONNECTION_IS_VALID(gc)) { + purple_debug_error("jabber", "Unable to set mood; account offline.\n"); + return; + } + + js = gc->proto_data; + + /* This is ugly, but protects us from unexpected values. */ + for (max_mood_idx = 0; moodstrings[max_mood_idx]; max_mood_idx++); + + if (selected_mood < 0 || selected_mood >= max_mood_idx) { + purple_debug_error("jabber", "Invalid mood index (%d) selected.\n", selected_mood); + return; + } + + jabber_mood_set(js, moodstrings[selected_mood], purple_request_fields_get_string(fields, "text")); } static void do_mood_set_mood(PurplePluginAction *action) {
--- a/libpurple/protocols/jabber/usertune.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/jabber/usertune.c Sat Nov 17 02:20:01 2007 +0000 @@ -35,7 +35,6 @@ xmlnode *tuneinfo, *tune; PurpleJabberTuneInfo tuneinfodata; JabberBuddyResource *resource; - const char *status_id; /* ignore the tune of people not on our buddy list */ if (!buddy || !item) @@ -81,9 +80,8 @@ } } } - status_id = jabber_buddy_state_get_status_id(resource->state); - purple_prpl_got_user_status(js->gc->account, from, status_id, + purple_prpl_got_user_status(js->gc->account, from, "tune", PURPLE_TUNE_ARTIST, tuneinfodata.artist, PURPLE_TUNE_TITLE, tuneinfodata.title, PURPLE_TUNE_ALBUM, tuneinfodata.album,
--- a/libpurple/protocols/msn/Makefile.am Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/msn/Makefile.am Sat Nov 17 02:20:01 2007 +0000 @@ -52,6 +52,8 @@ slpsession.h \ soap.c\ soap.h\ + soap2.c \ + soap2.h \ state.c \ state.h \ switchboard.c \
--- a/libpurple/protocols/msn/Makefile.mingw Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/msn/Makefile.mingw Sat Nov 17 02:20:01 2007 +0000 @@ -61,6 +61,7 @@ slpmsg.c \ slpsession.c \ soap.c\ + soap2.c\ state.c \ switchboard.c \ sync.c \
--- a/libpurple/protocols/msn/command.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/msn/command.c Sat Nov 17 02:20:01 2007 +0000 @@ -25,9 +25,9 @@ #include "command.h" static gboolean -is_num(char *str) +is_num(const char *str) { - char *c; + const char *c; for (c = str; *c; c++) { if (!(g_ascii_isdigit(*c))) return FALSE; @@ -42,9 +42,11 @@ * else return FALSE */ static gboolean -msn_check_payload_cmd(char *str) +msn_check_payload_cmd(const char *str) { - if( (!strcmp(str,"ADL")) || + g_return_val_if_fail(str != NULL, FALSE); + + if((!strcmp(str,"ADL")) || (!strcmp(str,"GCF")) || (!strcmp(str,"SG")) || (!strcmp(str,"MSG")) || @@ -84,16 +86,13 @@ msn_command_from_string(const char *string) { MsnCommand *cmd; - char *tmp; char *param_start; g_return_val_if_fail(string != NULL, NULL); - tmp = g_strdup(string); - param_start = strchr(tmp, ' '); - cmd = g_new0(MsnCommand, 1); - cmd->command = tmp; + cmd->command = g_strdup(string); + param_start = strchr(cmd->command, ' '); if (param_start) {
--- a/libpurple/protocols/msn/contact.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/msn/contact.c Sat Nov 17 02:20:01 2007 +0000 @@ -28,6 +28,7 @@ #include "contact.h" #include "xmlnode.h" #include "group.h" +#include "soap2.h" const char *MsnSoapPartnerScenarioText[] = { @@ -47,6 +48,11 @@ "Pending" }; +typedef struct { + MsnContact *contact; + MsnSoapPartnerScenario which; +} GetContactListCbData; + /* new a contact */ MsnContact * msn_contact_new(MsnSession *session) @@ -55,7 +61,6 @@ contact = g_new0(MsnContact, 1); contact->session = session; - contact->soapconn = msn_soap_new(session,contact,1); return contact; } @@ -64,15 +69,18 @@ void msn_contact_destroy(MsnContact *contact) { - msn_soap_destroy(contact->soapconn); g_free(contact); } MsnCallbackState * -msn_callback_state_new(void) +msn_callback_state_new(MsnSession *session) { - return g_new0(MsnCallbackState, 1); -} + MsnCallbackState *state = g_new0(MsnCallbackState, 1); + + state->session = session; + + return state; +} void msn_callback_state_free(MsnCallbackState *state) @@ -92,71 +100,56 @@ void msn_callback_state_set_who(MsnCallbackState *state, const gchar *who) { - gchar *new_str = NULL; - + gchar *nval; g_return_if_fail(state != NULL); - if (who != NULL) - new_str = g_strdup(who); - + nval = g_strdup(who); g_free(state->who); - state->who = new_str; + state->who = nval; } void msn_callback_state_set_uid(MsnCallbackState *state, const gchar *uid) { - gchar *new_str = NULL; - + gchar *nval; g_return_if_fail(state != NULL); - if (uid != NULL) - new_str = g_strdup(uid); - + nval = g_strdup(uid); g_free(state->uid); - state->uid = new_str; + state->uid = nval; } void msn_callback_state_set_old_group_name(MsnCallbackState *state, const gchar *old_group_name) { - gchar *new_str = NULL; - + gchar *nval; g_return_if_fail(state != NULL); - if (old_group_name != NULL) - new_str = g_strdup(old_group_name); - + nval = g_strdup(old_group_name); g_free(state->old_group_name); - state->old_group_name = new_str; + state->old_group_name = nval; } void msn_callback_state_set_new_group_name(MsnCallbackState *state, const gchar *new_group_name) { - gchar *new_str = NULL; - + gchar *nval; g_return_if_fail(state != NULL); - if (new_group_name != NULL) - new_str = g_strdup(new_group_name); - + nval = g_strdup(new_group_name); g_free(state->new_group_name); - state->new_group_name = new_str; + state->new_group_name = nval; } void msn_callback_state_set_guid(MsnCallbackState *state, const gchar *guid) { - gchar *new_str = NULL; - + gchar *nval; g_return_if_fail(state != NULL); - if (guid != NULL) - new_str = g_strdup(guid); - + nval = g_strdup(guid); g_free(state->guid); - state->guid = new_str; + state->guid = nval; } @@ -176,39 +169,9 @@ state->action |= action; } -/*contact SOAP server login error*/ -static void -msn_contact_login_error_cb(MsnSoapConn *soapconn, PurpleSslConnection *gsc, PurpleSslErrorType error) -{ - MsnSession *session; - - session = soapconn->session; - g_return_if_fail(session != NULL); - - msn_session_set_error(session, MSN_ERROR_SERV_DOWN, _("Unable to connect to contact server")); -} - -/*msn contact SOAP server connect process*/ -static gboolean -msn_contact_login_connect_cb(MsnSoapConn *soapconn, PurpleSslConnection *gsc) -{ - MsnSession * session; - MsnContact *contact; - - contact = soapconn->parent; - g_return_val_if_fail(contact != NULL, TRUE); - - session = contact->session; - g_return_val_if_fail(session != NULL, FALSE); - - /*login ok!We can retrieve the contact list*/ -// msn_get_contact_list(contact, MSN_PS_INITIAL, NULL); - return TRUE; -} - /*get MSN member role utility*/ static MsnListId -msn_get_memberrole(char *role) +msn_get_memberrole(const char *role) { g_return_val_if_fail(role != NULL, 0); @@ -244,37 +207,20 @@ } /* Create the AddressBook in the server, if we don't have one */ -static gboolean -msn_create_address_cb(MsnSoapConn *soapconn) +static void +msn_create_address_cb(MsnSoapMessage *req, MsnSoapMessage *resp, gpointer data) { - MsnContact *contact; - - if (soapconn->body == NULL) - return TRUE; - - contact = soapconn->parent; - g_return_val_if_fail(contact != NULL, TRUE); - - purple_debug_info("MSN AddressBook", "Address Book successfully created!\n"); - msn_get_address_book(contact, MSN_PS_INITIAL, NULL, NULL); - -// msn_soap_free_read_buf(soapconn); - return TRUE; -} - -static void -msn_create_address_written_cb(MsnSoapConn *soapconn) -{ - purple_debug_info("MSN AddressBook","AddressBookAdd written\n"); - soapconn->read_cb = msn_create_address_cb; - - return; + if (resp && msn_soap_xml_get(resp->xml, "Body/Fault") == NULL) { + purple_debug_info("msnab", "Address Book successfully created!\n"); + msn_get_address_book((MsnContact *)data, MSN_PS_INITIAL, NULL, NULL); + } else { + purple_debug_info("msnab", "Address Book creation failed!\n"); + } } static void msn_create_address_book(MsnContact * contact) { - MsnSoapReq *soap_request; gchar *body; g_return_if_fail(contact != NULL); @@ -282,323 +228,185 @@ g_return_if_fail(contact->session->user != NULL); g_return_if_fail(contact->session->user->passport != NULL); - purple_debug_info("MSN AddressBook","Creating an Address Book.\n"); + purple_debug_info("msnab","Creating an Address Book.\n"); body = g_strdup_printf(MSN_ADD_ADDRESSBOOK_TEMPLATE, contact->session->user->passport); - soap_request = msn_soap_request_new(MSN_CONTACT_SERVER, - MSN_ADDRESS_BOOK_POST_URL,MSN_ADD_ADDRESSBOOK_SOAP_ACTION, - body, - NULL, - msn_create_address_cb, - msn_create_address_written_cb, - msn_contact_connect_init); - msn_soap_post(contact->soapconn, soap_request); + msn_soap_message_send(contact->session, + msn_soap_message_new(MSN_ADD_ADDRESSBOOK_SOAP_ACTION, + xmlnode_from_str(body, -1)), + MSN_CONTACT_SERVER, MSN_ADDRESS_BOOK_POST_URL, msn_create_address_cb, + contact); g_free(body); - - return; +} + +static void +msn_parse_each_member(MsnSession *session, xmlnode *member, const char *node, + MsnListId list) +{ + char *passport = xmlnode_get_data(xmlnode_get_child(member, node)); + char *type = xmlnode_get_data(xmlnode_get_child(member, "Type")); + char *member_id = xmlnode_get_data(xmlnode_get_child(member, "MembershipId")); + MsnUser *user = msn_userlist_find_add_user(session->userlist, passport, NULL); + + purple_debug_info("msncl","%s name: %s, Type: %s, MembershipID: %s\n", + node, passport, type, member_id == NULL ? "(null)" : member_id); + + if (member_id) { + user->membership_id[list] = atoi(member_id); + } + + msn_got_lst_user(session, user, 1 << list, NULL); + + g_free(passport); + g_free(type); + g_free(member_id); +} + +static void +msn_parse_each_service(MsnSession *session, xmlnode *service) +{ + xmlnode *type; + + if ((type = msn_soap_xml_get(service, "Info/Handle/Type"))) { + char *type_str = xmlnode_get_data(type); + + if (g_str_equal(type_str, "Profile")) { + /* Process Windows Live 'Messenger Roaming Identity' */ + } else if (g_str_equal(type_str, "Messenger")) { + xmlnode *lastchange = xmlnode_get_child(service, "LastChange"); + char *lastchange_str = xmlnode_get_data(lastchange); + xmlnode *membership; + + purple_debug_info("msncl","last change: %s\n", lastchange_str); + purple_account_set_string(session->account, "CLLastChange", + lastchange_str); + + for (membership = msn_soap_xml_get(service, + "Memberships/Membership"); + membership; membership = xmlnode_get_next_twin(membership)) { + + xmlnode *role = xmlnode_get_child(membership, "MemberRole"); + char *role_str = xmlnode_get_data(role); + MsnListId list = msn_get_memberrole(role_str); + xmlnode *member; + + purple_debug_info("msncl", "MemberRole role: %s, list: %d\n", + role_str, list); + + for (member = msn_soap_xml_get(membership, "Members/Member"); + member; member = xmlnode_get_next_twin(member)) { + const char *member_type = xmlnode_get_attrib(member, "type"); + if (g_str_equal(member_type, "PassportMember")) { + msn_parse_each_member(session, member, "PassportName", + list); + } else if (g_str_equal(member_type, "PhoneMember")) { + + } else if (g_str_equal(member_type, "EmailMember")) { + msn_parse_each_member(session, member, "Email", list); + } + } + + g_free(role_str); + } + + g_free(lastchange_str); + } + + g_free(type_str); + } } /*parse contact list*/ static void -msn_parse_contact_list(MsnContact * contact) +msn_parse_contact_list(MsnContact *contact, xmlnode *node) { - MsnSession * session; - MsnListOp list_op = 0; - MsnListId list; - char * passport, *typedata; - xmlnode *fault, *faultstringnode, *faultdetail, *errorcode; - xmlnode *node, *body, *response, *result, *services; - xmlnode *service, *memberships, *info, *handle, *handletype; - xmlnode *membershipnode, *members, *member, *passportNode; - - session = contact->session; - node = xmlnode_from_str(contact->soapconn->body, contact->soapconn->body_len); - - if (node == NULL) { - purple_debug_error("MSNCL","Unable to parse SOAP data!\n"); - return; - } - - purple_debug_misc("MSNCL","Parsing contact list with size %d\n", contact->soapconn->body_len); + xmlnode *fault, *faultnode; - purple_debug_misc("MSNCL","Root node @ %p: Name: '%s', child: '%s', lastchild: '%s'\n", node, - node->name ? node->name : "(null)", - (node->child && node->child->name) ? node->child->name : "(null)", - (node->lastchild && node->lastchild->name) ? node->lastchild->name : "(null)"); - - body = xmlnode_get_child(node, "Body"); - - if (body == NULL) { - purple_debug_warning("MSNCL", "Failed to parse contact list Body node\n"); - xmlnode_free(node); - return; - } - purple_debug_info("MSNCL","Body @ %p: Name: '%s'\n",body,body->name); - - /* Did we receive a <Fault> ? */ - if ( (fault = xmlnode_get_child(body, "Fault")) != NULL) { - purple_debug_info("MSNCL","Fault received from SOAP server!\n"); - - if ( (faultstringnode = xmlnode_get_child(fault, "faultstring")) != NULL ) { - gchar * faultstring = xmlnode_get_data(faultstringnode); - purple_debug_info("MSNCL", "Faultstring: %s\n", faultstring ? faultstring : "(null)"); + /* we may get a response if our cache data is too old: + * + * <faultstring>Need to do full sync. Can't sync deltas Client + * has too old a copy for us to do a delta sync</faultstring> + * + * this is not handled yet + */ + if ((fault = msn_soap_xml_get(node, "Body/Fault"))) { + if ((faultnode = xmlnode_get_child(fault, "faultstring"))) { + char *faultstring = xmlnode_get_data(faultnode); + purple_debug_info("msncl", "Retrieving contact list failed: %s\n", + faultstring); g_free(faultstring); } - if ( (faultdetail = xmlnode_get_child(fault, "detail")) != NULL ) { - purple_debug_info("MSNCL","detail @ %p, name: %s\n",faultdetail, faultdetail->name); - - if ( (errorcode = xmlnode_get_child(faultdetail, "errorcode")) != NULL ) { - purple_debug_info("MSNCL","errorcode @ %p, name: %s\n", errorcode, errorcode->name); - - if (errorcode->child != NULL) { - gchar *errorcodestring = xmlnode_get_data(errorcode); - purple_debug_info("MSNCL", "Error Code: %s\n", errorcodestring ? errorcodestring : "(null)"); - - if (errorcodestring && !strncmp(errorcodestring, "ABDoesNotExist", 14) ) { - xmlnode_free(node); - g_free(errorcodestring); - msn_create_address_book(contact); - return; - } - g_free(errorcodestring); - } - } - } - xmlnode_free(node); - msn_get_contact_list(contact, MSN_PS_INITIAL, NULL); - return; - } - - response = xmlnode_get_child(body,"FindMembershipResponse"); + if ((faultnode = msn_soap_xml_get(fault, "detail/errorcode"))) { + char *errorcode = xmlnode_get_data(faultnode); - if (response == NULL) { - /* we may get a response if our cache data is too old: - * - * <faultstring>Need to do full sync. Can't sync deltas Client - * has too old a copy for us to do a delta sync</faultstring> - */ - xmlnode_free(node); - msn_get_contact_list(contact, MSN_PS_INITIAL, NULL); - return; - } - purple_debug_info("MSNCL","FindMembershipResponse @ %p: Name: '%s'\n",response,response->name); - - result = xmlnode_get_child(response,"FindMembershipResult"); - if (result == NULL) { - purple_debug_warning("MSNCL","Received No Update!\n"); - xmlnode_free(node); - return; - } - purple_debug_info("MSNCL","Result @ %p: Name: '%s'\n", result, result->name); + if (g_str_equal(errorcode, "ABDoesNotExist")) { + msn_create_address_book(contact); + g_free(errorcode); + return; + } - if ( (services = xmlnode_get_child(result,"Services")) == NULL) { - purple_debug_misc("MSNCL","No <Services> received.\n"); - xmlnode_free(node); - return; - } - - purple_debug_info("MSNCL","Services @ %p\n",services); - - for (service = xmlnode_get_child(services, "Service"); service; - service = xmlnode_get_next_twin(service)) { - purple_debug_info("MSNCL","Service @ %p\n",service); - - if ( (info = xmlnode_get_child(service,"Info")) == NULL ) { - purple_debug_error("MSNCL","Error getting 'Info' child node\n"); - continue; - } - if ( (handle = xmlnode_get_child(info,"Handle")) == NULL ) { - purple_debug_error("MSNCL","Error getting 'Handle' child node\n"); - continue; - } - if ( (handletype = xmlnode_get_child(handle,"Type")) == NULL ) { - purple_debug_error("MSNCL","Error getting 'Type' child node\n"); - continue; + g_free(errorcode); } - if ( (typedata = xmlnode_get_data(handletype)) == NULL) { - purple_debug_error("MSNCL","Error retrieving data from 'Type' child node\n"); - continue; - } - - purple_debug_info("MSNCL","processing '%s' Service\n", typedata); - - if ( !g_strcasecmp(typedata, "Profile") ) { - /* Process Windows Live 'Messenger Roaming Identity' */ - g_free(typedata); - continue; - } - - if ( !g_strcasecmp(typedata, "Messenger") ) { - char *LastChangeStr = NULL; - xmlnode *LastChangeNode; - - /*Last Change Node*/ - if ((LastChangeNode = xmlnode_get_child(service, "LastChange"))) - LastChangeStr = xmlnode_get_data(LastChangeNode); - purple_debug_info("MSNCL","LastChangeNode: '%s'\n",LastChangeStr ? LastChangeStr : "(null)"); - purple_account_set_string(session->account, "CLLastChange", LastChangeStr); - g_free(LastChangeStr); + msn_get_contact_list(contact, MSN_PS_INITIAL, NULL); + } else { + xmlnode *service; - memberships = xmlnode_get_child(service,"Memberships"); - if (memberships == NULL) { - purple_debug_warning("MSNCL","Memberships = NULL, cleaning up and returning.\n"); - g_free(typedata); - xmlnode_free(node); - return; - } - purple_debug_info("MSNCL","Memberships @ %p: Name: '%s'\n",memberships,memberships->name); - for (membershipnode = xmlnode_get_child(memberships, "Membership"); membershipnode; - membershipnode = xmlnode_get_next_twin(membershipnode)){ - xmlnode *roleNode; - char *role = NULL; - list = 0; + for (service = msn_soap_xml_get(node, "Body/FindMembershipResponse/" + "FindMembershipResult/Services/Service"); + service; service = xmlnode_get_next_twin(service)) { + msn_parse_each_service(contact->session, service); + } + } +} - if ((roleNode = xmlnode_get_child(membershipnode,"MemberRole"))) { - role = xmlnode_get_data(roleNode); - list = msn_get_memberrole(role); - } - list_op = 1 << list; - - purple_debug_info("MSNCL","MemberRole role: %s, list_op: %d\n", role ? role : "(null)", list_op); +static void +msn_get_contact_list_cb(MsnSoapMessage *req, MsnSoapMessage *resp, + gpointer data) +{ + GetContactListCbData *cb_data = data; + MsnContact *contact = cb_data->contact; + MsnSession *session = contact->session; - g_free(role); - - members = xmlnode_get_child(membershipnode, "Members"); - for (member = xmlnode_get_child(members, "Member"); member; - member = xmlnode_get_next_twin(member)){ - MsnUser *user = NULL; - xmlnode *typeNode, *membershipIdNode = NULL; - gchar *type, *membershipId = NULL; - const char *member_type = xmlnode_get_attrib(member, "type"); + g_return_if_fail(session != NULL); - if (!member_type) { - purple_debug_error("msn", "No Member Type specified for Member.\n"); - continue; - } + if (resp != NULL) { + const char *abLastChange; + const char *dynamicItemLastChange; - if(!g_strcasecmp(member_type, "PassportMember") ) { - passport = type = NULL; - if ((passportNode = xmlnode_get_child(member, "PassportName"))) - passport = xmlnode_get_data(passportNode); - if ((typeNode = xmlnode_get_child(member, "Type"))) - type = xmlnode_get_data(typeNode); - purple_debug_info("MSNCL","Passport name: '%s', Type: %s\n", passport ? passport : "(null)", type ? type : "(null)"); - /* Why do we even bother parsing it just to free it??? */ - g_free(type); - - user = msn_userlist_find_add_user(session->userlist,passport,NULL); - g_free(passport); - - membershipIdNode = xmlnode_get_child(member,"MembershipId"); - if (membershipIdNode != NULL) { - membershipId = xmlnode_get_data(membershipIdNode); - if (membershipId != NULL) { - user->membership_id[list] = atoi(membershipId); - g_free(membershipId); - } - } + purple_debug_misc("msncl","Got the contact list!\n"); - msn_got_lst_user(session, user, list_op, NULL); - } - else if (!g_strcasecmp(member_type, "PhoneMember")) { - purple_debug_info("msn", "Recieved Phone Member; ignoring.\n"); - } - else if (!g_strcasecmp(member_type, "EmailMember")) { - xmlnode *emailNode; - passport = NULL; - - if ((emailNode = xmlnode_get_child(member, "Email"))) - passport = xmlnode_get_data(emailNode); - purple_debug_info("MSNCL","Email Member: Name: '%s', list_op: %d\n", passport ? passport : "(null)", list_op); - - user = msn_userlist_find_add_user(session->userlist, passport, NULL); - g_free(passport); + msn_parse_contact_list(cb_data->contact, resp->xml); + abLastChange = purple_account_get_string(session->account, + "ablastChange", NULL); + dynamicItemLastChange = purple_account_get_string(session->account, + "dynamicItemLastChange", NULL); - membershipIdNode = xmlnode_get_child(member,"MembershipId"); - if (membershipIdNode != NULL) { - membershipId = xmlnode_get_data(membershipIdNode); - if (membershipId != NULL) { - user->membership_id[list] = atoi(membershipId); - g_free(membershipId); - } - } - - msn_got_lst_user(session, user, list_op, NULL); - } else { - purple_debug_info("msn", "Unknown Member type: %s\n", member_type); - } - } - } - g_free(typedata); /* Free 'Type' node data after processing 'Messenger' Service */ + if (cb_data->which == MSN_PS_INITIAL) { +#ifdef MSN_PARTIAL_LISTS + /* XXX: this should be enabled when we can correctly do partial + syncs with the server. Currently we need to retrieve the whole + list to detect sync issues */ + msn_get_address_book(contact, MSN_PS_INITIAL, abLastChange, dynamicItemLastChange); +#else + msn_get_address_book(contact, MSN_PS_INITIAL, NULL, NULL); +#endif } } - xmlnode_free(node); /* Free the whole XML tree */ + g_free(cb_data); } -static gboolean -msn_get_contact_list_cb(MsnSoapConn *soapconn) +/*SOAP get contact list*/ +void +msn_get_contact_list(MsnContact * contact, + const MsnSoapPartnerScenario partner_scenario, const char *update_time) { - MsnContact *contact; - MsnSession *session; - const char *abLastChange; - const char *dynamicItemLastChange; - gchar *partner_scenario; - - if (soapconn->body == NULL) - return TRUE; - - purple_debug_misc("MSNCL","Got the contact list!\n"); - - contact = soapconn->parent; - g_return_val_if_fail(contact != NULL, TRUE); - session = soapconn->session; - g_return_val_if_fail(session != NULL, FALSE); - g_return_val_if_fail(soapconn->data_cb != NULL, TRUE); - - partner_scenario = soapconn->data_cb; - - msn_parse_contact_list(contact); - /*free the read buffer*/ - msn_soap_free_read_buf(soapconn); - - abLastChange = purple_account_get_string(session->account, "ablastChange", NULL); - dynamicItemLastChange = purple_account_get_string(session->account, "dynamicItemLastChange", NULL); - - if (!strcmp(partner_scenario, MsnSoapPartnerScenarioText[MSN_PS_INITIAL])) { - -#ifdef MSN_PARTIAL_LISTS - /* XXX: this should be enabled when we can correctly do partial - syncs with the server. Currently we need to retrieve the whole - list to detect sync issues */ - msn_get_address_book(contact, MSN_PS_INITIAL, abLastChange, dynamicItemLastChange); -#else - msn_get_address_book(contact, MSN_PS_INITIAL, NULL, NULL); -#endif - } else { - msn_soap_free_read_buf(soapconn); - } - - return TRUE; -} - -static void -msn_get_contact_written_cb(MsnSoapConn *soapconn) -{ - purple_debug_misc("MSNCL","Sent SOAP request for the contact list.\n"); - soapconn->read_cb = msn_get_contact_list_cb; -} - -/* SOAP get contact list*/ -void -msn_get_contact_list(MsnContact * contact, const MsnSoapPartnerScenario partner_scenario, const char *update_time) -{ - MsnSoapReq *soap_request; - gchar *body; + gchar *body = NULL; gchar *update_str = NULL; + GetContactListCbData cb_data = { contact, partner_scenario }; const gchar *partner_scenario_str = MsnSoapPartnerScenarioText[partner_scenario]; purple_debug_misc("MSNCL","Getting Contact List.\n"); @@ -609,17 +417,14 @@ } body = g_strdup_printf(MSN_GET_CONTACT_TEMPLATE, partner_scenario_str, update_str ? update_str : ""); - g_free(update_str); - soap_request = msn_soap_request_new(MSN_CONTACT_SERVER, - MSN_GET_CONTACT_POST_URL, - MSN_GET_CONTACT_SOAP_ACTION, - body, - (gpointer) partner_scenario_str, - msn_get_contact_list_cb, - msn_get_contact_written_cb, - msn_contact_connect_init); - msn_soap_post(contact->soapconn,soap_request); + msn_soap_message_send(contact->session, + msn_soap_message_new(MSN_GET_CONTACT_SOAP_ACTION, + xmlnode_from_str(body, -1)), + MSN_CONTACT_SERVER, MSN_GET_CONTACT_POST_URL, + msn_get_contact_list_cb, g_memdup(&cb_data, sizeof(cb_data))); + + g_free(update_str); g_free(body); } @@ -629,7 +434,7 @@ MsnSession *session = contact->session; xmlnode *group; - purple_debug_info("MsnAb","msn_parse_addressbook_groups()\n"); + purple_debug_info("MSNAB","msn_parse_addressbook_groups()\n"); for(group = xmlnode_get_child(node, "Group"); group; group = xmlnode_get_next_twin(group)){ @@ -781,81 +586,48 @@ } static gboolean -msn_parse_addressbook(MsnContact * contact) +msn_parse_addressbook(MsnContact * contact, xmlnode *node) { - MsnSession *session; - xmlnode * node,*body,*response,*result; + MsnSession * session; + xmlnode *result; xmlnode *groups; xmlnode *contacts; xmlnode *abNode; - xmlnode *fault, *faultstringnode, *faultdetail, *errorcode; + xmlnode *fault; session = contact->session; - node = xmlnode_from_str(contact->soapconn->body, contact->soapconn->body_len); - if ( node == NULL ) { - purple_debug_error("MSN AddressBook","Error parsing Address Book with size %d\n", contact->soapconn->body_len); + if ((fault = msn_soap_xml_get(node, "Body/Fault"))) { + xmlnode *faultnode; + + if ((faultnode = xmlnode_get_child(fault, "faultstring"))) { + gchar *faultstring = xmlnode_get_data(faultnode); + purple_debug_info("MSNAB","Faultstring: %s\n", faultstring); + g_free(faultstring); + } + + if ((faultnode = msn_soap_xml_get(fault, "detail/errorcode"))) { + gchar *errorcode = xmlnode_get_data(faultnode); + + purple_debug_info("MSNAB", "Error Code: %s\n", errorcode); + + if (g_str_equal(errorcode, "ABDoesNotExist")) { + g_free(errorcode); + return TRUE; + } + } + return FALSE; } - purple_debug_misc("MSN AddressBook", "Parsing Address Book with size %d\n", contact->soapconn->body_len); - - purple_debug_misc("MSN AddressBook","node{%p},name:%s,child:%s,last:%s\n", node, - node->name ? node->name : "(null)", - (node->child && node->child->name) ? node->child->name : "(null)", - (node->lastchild && node->lastchild->name) ? node->lastchild->name : "(null)"); - - body = xmlnode_get_child(node,"Body"); - purple_debug_misc("MSN AddressBook","body{%p},name:%s\n",body,body->name); - - /* TODO: This appears to be used in a number of places and should be de-duplicated */ - if ( (fault = xmlnode_get_child(body, "Fault")) != NULL) { - purple_debug_info("MSN AddressBook","Fault received from SOAP server!\n"); - - if ( (faultstringnode = xmlnode_get_child(fault, "faultstring")) != NULL ) { - gchar *faultstring = xmlnode_get_data(faultstringnode); - purple_debug_info("MSN AddressBook","Faultstring: %s\n", faultstring ? faultstring : "(null)"); - g_free(faultstring); - } - if ( (faultdetail = xmlnode_get_child(fault, "detail")) != NULL ) { - purple_debug_info("MSN AddressBook","detail @ %p, name: %s\n",faultdetail, faultdetail->name); - - if ( (errorcode = xmlnode_get_child(faultdetail, "errorcode")) != NULL ) { - gchar *errorcodestring; - purple_debug_info("MSN AddressBook","errorcode @ %p, name: %s\n",errorcode, errorcode->name); - - errorcodestring = xmlnode_get_data(errorcode); - purple_debug_info("MSN AddressBook", "Error Code: %s\n", errorcodestring ? errorcodestring : "(null)"); - - if (errorcodestring && !strncmp(errorcodestring, "ABDoesNotExist", 14) ) { - g_free(errorcodestring); - xmlnode_free(node); - return TRUE; - } - g_free(errorcodestring); - } - } - xmlnode_free(node); - return FALSE; + result = msn_soap_xml_get(node, "Body/ABFindAllResponse/ABFindAllResult"); + if(result == NULL){ + purple_debug_misc("MSNAB","receive no address book update\n"); + return TRUE; } - - response = xmlnode_get_child(body,"ABFindAllResponse"); - - if (response == NULL) { - xmlnode_free(node); - return FALSE; - } - - purple_debug_misc("MSN SOAP","response{%p},name:%s\n",response,response->name); - result = xmlnode_get_child(response,"ABFindAllResult"); - if(result == NULL){ - purple_debug_misc("MSNAB","receive no address book update\n"); - xmlnode_free(node); - return TRUE; - } - purple_debug_info("MSN SOAP","result{%p},name:%s\n",result,result->name); - + /* I don't see this "groups" tag documented on msnpiki, need to find out + if they are really there, and update msnpiki */ /*Process Group List*/ groups = xmlnode_get_child(result,"groups"); if (groups != NULL) { @@ -865,7 +637,7 @@ /*add a default No group to set up the no group Membership*/ msn_group_new(session->userlist, MSN_INDIVIDUALS_GROUP_ID, MSN_INDIVIDUALS_GROUP_NAME); - purple_debug_misc("MsnAB","group_id:%s name:%s\n", + purple_debug_misc("MSNAB","group_id:%s name:%s\n", MSN_INDIVIDUALS_GROUP_ID, MSN_INDIVIDUALS_GROUP_NAME); if ((purple_find_group(MSN_INDIVIDUALS_GROUP_NAME)) == NULL){ PurpleGroup *g = purple_group_new(MSN_INDIVIDUALS_GROUP_NAME); @@ -874,7 +646,7 @@ /*add a default No group to set up the no group Membership*/ msn_group_new(session->userlist, MSN_NON_IM_GROUP_ID, MSN_NON_IM_GROUP_NAME); - purple_debug_misc("MsnAB","group_id:%s name:%s\n", MSN_NON_IM_GROUP_ID, MSN_NON_IM_GROUP_NAME); + purple_debug_misc("MSNAB","group_id:%s name:%s\n", MSN_NON_IM_GROUP_ID, MSN_NON_IM_GROUP_NAME); if ((purple_find_group(MSN_NON_IM_GROUP_NAME)) == NULL){ PurpleGroup *g = purple_group_new(MSN_NON_IM_GROUP_NAME); purple_blist_add_group(g, NULL); @@ -894,7 +666,7 @@ if ((node2 = xmlnode_get_child(abNode, "lastChange"))) tmp = xmlnode_get_data(node2); - purple_debug_info("MsnAB"," lastchanged Time:{%s}\n", tmp ? tmp : "(null)"); + purple_debug_info("MSNAB"," lastchanged Time:{%s}\n", tmp ? tmp : "(null)"); purple_account_set_string(session->account, "ablastChange", tmp); g_free(tmp); tmp = NULL; @@ -905,68 +677,51 @@ g_free(tmp); } - xmlnode_free(node); - msn_soap_free_read_buf(contact->soapconn); return TRUE; } -static gboolean -msn_get_address_cb(MsnSoapConn *soapconn) +static void +msn_get_address_cb(MsnSoapMessage *req, MsnSoapMessage *resp, gpointer data) { - MsnContact *contact; + MsnContact *contact = data; MsnSession *session; - if (soapconn->body == NULL) - return TRUE; + if (resp == NULL) + return; - contact = soapconn->parent; - g_return_val_if_fail(contact != NULL, TRUE); - session = soapconn->session; - g_return_val_if_fail(session != NULL, FALSE); + g_return_if_fail(contact != NULL); + session = contact->session; + g_return_if_fail(session != NULL); - purple_debug_misc("MSN AddressBook", "Got the Address Book!\n"); + purple_debug_misc("MSNAB", "Got the Address Book!\n"); - if ( msn_parse_addressbook(contact) ) { - //msn_soap_free_read_buf(soapconn); - + if (msn_parse_addressbook(contact, resp->xml)) { if (!session->logged_in) { msn_send_privacy(session->account->gc); msn_notification_dump_contact(session); } - - /*free the read buffer*/ - msn_soap_free_read_buf(soapconn); - return TRUE; } else { - /* This is making us loop infinitely when we fail to parse the address book, - disable for now (we should re-enable when we send timestamps) + /* This is making us loop infinitely when we fail to parse the + address book, disable for now (we should re-enable when we + send timestamps) */ /* msn_get_address_book(contact, NULL, NULL); */ msn_session_disconnect(session); - purple_connection_error(session->account->gc, _("Unable to retrieve MSN Address Book")); - return FALSE; + purple_connection_error_reason(session->account->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Unable to retrieve MSN Address Book")); } } -/**/ -static void -msn_address_written_cb(MsnSoapConn *soapconn) -{ - purple_debug_misc("MSN AddressBook","Sent SOAP request for the Address Book.\n"); - soapconn->read_cb = msn_get_address_cb; -} - /*get the address book*/ void -msn_get_address_book(MsnContact *contact, const MsnSoapPartnerScenario partner_scenario, const char *LastChanged, const char *dynamicItemLastChange) +msn_get_address_book(MsnContact *contact, + MsnSoapPartnerScenario partner_scenario, const char *LastChanged, + const char *dynamicItemLastChange) { - MsnSoapReq *soap_request; - char *body; - char *update_str = NULL; + char *body, *update_str = NULL; - purple_debug_misc("MSN AddressBook","Getting Address Book\n"); + purple_debug_misc("MSNAB","Getting Address Book\n"); /*build SOAP and POST it*/ if (dynamicItemLastChange != NULL) @@ -974,84 +729,58 @@ else if (LastChanged != NULL) update_str = g_strdup_printf(MSN_GET_ADDRESS_UPDATE_XML, LastChanged); - body = g_strdup_printf(MSN_GET_ADDRESS_TEMPLATE, MsnSoapPartnerScenarioText[partner_scenario], update_str ? update_str : ""); - g_free(update_str); - soap_request = msn_soap_request_new(MSN_CONTACT_SERVER, - MSN_ADDRESS_BOOK_POST_URL,MSN_GET_ADDRESS_SOAP_ACTION, - body, - NULL, - msn_get_address_cb, - msn_address_written_cb, - msn_contact_connect_init); - msn_soap_post(contact->soapconn,soap_request); + msn_soap_message_send(contact->session, + msn_soap_message_new(MSN_GET_ADDRESS_SOAP_ACTION, + xmlnode_from_str(body, -1)), + MSN_CONTACT_SERVER, MSN_ADDRESS_BOOK_POST_URL, msn_get_address_cb, + contact); + + g_free(update_str); g_free(body); } -static gboolean -msn_add_contact_read_cb(MsnSoapConn *soapconn) +static void +msn_add_contact_read_cb(MsnSoapMessage *req, MsnSoapMessage *resp, + gpointer data) { - MsnCallbackState *state = NULL; - MsnUserList *userlist; - MsnUser *user; - - g_return_val_if_fail(soapconn->data_cb != NULL, TRUE); - g_return_val_if_fail(soapconn->session != NULL, FALSE); - g_return_val_if_fail(soapconn->session->userlist != NULL, TRUE); + MsnCallbackState *state = data; + MsnSession *session = state->session; - state = (MsnCallbackState *) soapconn->data_cb; + g_return_if_fail(session != NULL); - if (soapconn->body == NULL) { - msn_callback_state_free(state); - return TRUE; - } + if (resp != NULL) { + MsnUserList *userlist = session->userlist; + MsnUser *user; - userlist = soapconn->session->userlist; - - purple_debug_info("MSNCL","Contact added successfully\n"); - - // the code this block is replacing didn't send ADL for yahoo contacts, - // but i haven't confirmed this is WLM's behaviour wrt yahoo contacts + purple_debug_info("MSNCL","Contact added successfully\n"); - if ( !msn_user_is_yahoo(soapconn->session->account, state->who) ) { - - msn_userlist_add_buddy_to_list(userlist, state->who, MSN_LIST_AL); - msn_userlist_add_buddy_to_list(userlist, state->who, MSN_LIST_FL); - } - msn_notification_send_fqy(soapconn->session, state->who); - - user = msn_userlist_find_add_user(userlist, state->who, state->who); - msn_user_add_group_id(user, state->guid); + // the code this block is replacing didn't send ADL for yahoo contacts, + // but i haven't confirmed this is WLM's behaviour wrt yahoo contacts + if ( !msn_user_is_yahoo(session->account, state->who) ) { + msn_userlist_add_buddy_to_list(userlist, state->who, MSN_LIST_AL); + msn_userlist_add_buddy_to_list(userlist, state->who, MSN_LIST_FL); + } - if (msn_userlist_user_is_in_list(user, MSN_LIST_PL)) { - msn_del_contact_from_list(soapconn->session->contact, NULL, state->who, MSN_LIST_PL); - } else { - msn_soap_free_read_buf(soapconn); - } - - msn_callback_state_free(state); + msn_notification_send_fqy(session, state->who); - return TRUE; -} + user = msn_userlist_find_add_user(userlist, state->who, state->who); + msn_user_add_group_id(user, state->guid); + } -static void -msn_add_contact_written_cb(MsnSoapConn *soapconn) -{ - purple_debug_info("MSNCL","Add contact request written\n"); - soapconn->read_cb = msn_add_contact_read_cb; + msn_callback_state_free(state); } /* add a Contact in MSN_INDIVIDUALS_GROUP */ void msn_add_contact(MsnContact *contact, MsnCallbackState *state, const char *passport) { - MsnSoapReq *soap_request; gchar *body = NULL; gchar *contact_xml = NULL; - g_return_if_fail(passport != NULL); -/* gchar *escaped_displayname; +#if 0 + gchar *escaped_displayname; if (displayname != NULL) { @@ -1060,94 +789,71 @@ escaped_displayname = passport; } contact_xml = g_strdup_printf(MSN_XML_ADD_CONTACT, escaped_displayname, passport); -*/ +#endif + purple_debug_info("MSNCL","Adding contact %s to contact list\n", passport); -// if ( !strcmp(state->guid, MSN_INDIVIDUALS_GROUP_ID) ) { - contact_xml = g_strdup_printf(MSN_CONTACT_XML, passport); -// } + contact_xml = g_strdup_printf(MSN_CONTACT_XML, passport); body = g_strdup_printf(MSN_ADD_CONTACT_TEMPLATE, contact_xml); + msn_soap_message_send(contact->session, + msn_soap_message_new(MSN_CONTACT_ADD_SOAP_ACTION, + xmlnode_from_str(body, -1)), + MSN_CONTACT_SERVER, MSN_ADDRESS_BOOK_POST_URL, + msn_add_contact_read_cb, state); + g_free(contact_xml); - - /*build SOAP and POST it*/ - - soap_request = msn_soap_request_new(MSN_CONTACT_SERVER, - MSN_ADDRESS_BOOK_POST_URL, - MSN_CONTACT_ADD_SOAP_ACTION, - body, - state, - msn_add_contact_read_cb, - msn_add_contact_written_cb, - msn_contact_connect_init); - msn_soap_post(contact->soapconn,soap_request); - g_free(body); } -static gboolean -msn_add_contact_to_group_read_cb(MsnSoapConn *soapconn) +static void +msn_add_contact_to_group_read_cb(MsnSoapMessage *req, MsnSoapMessage *resp, + gpointer data) { - MsnCallbackState *state; + MsnCallbackState *state = data; MsnUserList *userlist; - g_return_val_if_fail(soapconn->data_cb != NULL, TRUE); - g_return_val_if_fail(soapconn->session != NULL, FALSE); - g_return_val_if_fail(soapconn->session->userlist != NULL, TRUE); + g_return_if_fail(data != NULL); + + userlist = state->session->userlist; - userlist = soapconn->session->userlist; + if (resp != NULL) { + if (msn_userlist_add_buddy_to_group(userlist, state->who, + state->new_group_name)) { + purple_debug_info("MSNCL", "Contact %s added to group %s successfully!\n", state->who, state->new_group_name); + } else { + purple_debug_info("MSNCL","Contact %s added to group %s successfully on server, but failed in the local list\n", state->who, state->new_group_name); + } - state = (MsnCallbackState *) soapconn->data_cb; + if (state->action & MSN_ADD_BUDDY) { + MsnUser *user = msn_userlist_find_user(userlist, state->who); - if (soapconn->body == NULL) { - msn_callback_state_free(state); - return TRUE; - } - - if (msn_userlist_add_buddy_to_group(userlist, state->who, state->new_group_name) == TRUE) { - purple_debug_info("MSNCL", "Contact %s added to group %s successfully!\n", state->who, state->new_group_name); - } else { - purple_debug_info("MSNCL","Contact %s added to group %s successfully on server, but failed in the local list\n", state->who, state->new_group_name); - } + if ( !msn_user_is_yahoo(state->session->account, state->who) ) { + + msn_userlist_add_buddy_to_list(userlist, state->who, MSN_LIST_AL); + msn_userlist_add_buddy_to_list(userlist, state->who, MSN_LIST_FL); + } + msn_notification_send_fqy(state->session, state->who); - if (state->action & MSN_ADD_BUDDY) { - MsnUser *user = msn_userlist_find_user(userlist, state->who); - - if ( !msn_user_is_yahoo(soapconn->session->account, state->who) ) { + if (msn_userlist_user_is_in_list(user, MSN_LIST_PL)) { + msn_del_contact_from_list(state->session->contact, NULL, state->who, MSN_LIST_PL); + msn_callback_state_free(state); + return; + } + } - msn_userlist_add_buddy_to_list(userlist, state->who, MSN_LIST_AL); - msn_userlist_add_buddy_to_list(userlist, state->who, MSN_LIST_FL); - } - msn_notification_send_fqy(soapconn->session, state->who); - - if (msn_userlist_user_is_in_list(user, MSN_LIST_PL)) { - msn_del_contact_from_list(soapconn->session->contact, NULL, state->who, MSN_LIST_PL); - msn_callback_state_free(state); - return TRUE; + if (state->action & MSN_MOVE_BUDDY) { + msn_del_contact_from_group(state->session->contact, state->who, state->old_group_name); } } - if (state->action & MSN_MOVE_BUDDY) { - msn_del_contact_from_group(soapconn->session->contact, state->who, state->old_group_name); - } else { - msn_callback_state_free(state); - msn_soap_free_read_buf(soapconn); - } - return TRUE; -} - -static void -msn_add_contact_to_group_written_cb(MsnSoapConn *soapconn) -{ - purple_debug_info("MSNCL","Add contact to group request sent!\n"); - soapconn->read_cb = msn_add_contact_to_group_read_cb; + msn_callback_state_free(state); } void msn_add_contact_to_group(MsnContact *contact, MsnCallbackState *state, const char *passport, const char *groupId) { - MsnSoapReq *soap_request; MsnUserList *userlist; MsnUser *user; gchar *body = NULL, *contact_xml; @@ -1180,76 +886,52 @@ return; } - purple_debug_info("MSNCL", "Adding user %s to group %s\n", passport, msn_userlist_find_group_name(userlist, groupId)); user = msn_userlist_find_user(userlist, passport); if (user == NULL) { - purple_debug_warning("MSN CL", "Unable to retrieve user %s from the userlist!\n", passport); + purple_debug_warning("MSNCL", "Unable to retrieve user %s from the userlist!\n", passport); + msn_callback_state_free(state); + return; /* guess this never happened! */ } - if (user->uid != NULL) { + if (user != NULL && user->uid != NULL) { contact_xml = g_strdup_printf(MSN_CONTACT_ID_XML, user->uid); } else { contact_xml = g_strdup_printf(MSN_CONTACT_XML, passport); } body = g_strdup_printf(MSN_ADD_CONTACT_GROUP_TEMPLATE, groupId, contact_xml); - g_free(contact_xml); - - /*build SOAP and POST it*/ - soap_request = msn_soap_request_new(MSN_CONTACT_SERVER, - MSN_ADDRESS_BOOK_POST_URL, - MSN_ADD_CONTACT_GROUP_SOAP_ACTION, - body, - state, - msn_add_contact_to_group_read_cb, - msn_add_contact_to_group_written_cb, - msn_contact_connect_init); - msn_soap_post(contact->soapconn,soap_request); + msn_soap_message_send(state->session, + msn_soap_message_new(MSN_ADD_CONTACT_GROUP_SOAP_ACTION, + xmlnode_from_str(body, -1)), + MSN_CONTACT_SERVER, MSN_ADDRESS_BOOK_POST_URL, + msn_add_contact_to_group_read_cb, state); + g_free(contact_xml); g_free(body); } - - -static gboolean -msn_delete_contact_read_cb(MsnSoapConn *soapconn) +static void +msn_delete_contact_read_cb(MsnSoapMessage *req, MsnSoapMessage *resp, + gpointer data) { - MsnUser *user; - MsnCallbackState *state = (MsnCallbackState *) soapconn->data_cb; - MsnUserList *userlist; - - g_return_val_if_fail(soapconn->session != NULL, FALSE); - g_return_val_if_fail(soapconn->session->userlist != NULL, TRUE); + MsnCallbackState *state = data; - userlist = soapconn->session->userlist; + if (resp != NULL) { + MsnUserList *userlist = state->session->userlist; + MsnUser *user = msn_userlist_find_user_with_id(userlist, state->uid); - if (soapconn->body == NULL) { - msn_callback_state_free(state); - return TRUE; - } + purple_debug_info("MSNCL","Delete contact successful\n"); - purple_debug_info("MSNCL","Delete contact successful\n"); - - user = msn_userlist_find_user_with_id(userlist, state->uid); - if (user != NULL) { - msn_userlist_remove_user(userlist, user); + if (user != NULL) { + msn_userlist_remove_user(userlist, user); + } } msn_callback_state_free(state); - msn_soap_free_read_buf(soapconn); - - return TRUE; -} - -static void -msn_delete_contact_written_cb(MsnSoapConn *soapconn) -{ - purple_debug_info("MSNCL","Delete contact request written\n"); - soapconn->read_cb = msn_delete_contact_read_cb; } /*delete a Contact*/ @@ -1258,72 +940,52 @@ { gchar *body = NULL; gchar *contact_id_xml = NULL ; - MsnSoapReq *soap_request; MsnCallbackState *state; g_return_if_fail(contactId != NULL); contact_id_xml = g_strdup_printf(MSN_CONTACT_ID_XML, contactId); - state = msn_callback_state_new(); + state = msn_callback_state_new(contact->session); msn_callback_state_set_uid(state, contactId); /* build SOAP request */ purple_debug_info("MSNCL","Deleting contact with contactId: %s\n", contactId); body = g_strdup_printf(MSN_DEL_CONTACT_TEMPLATE, contact_id_xml); - soap_request = msn_soap_request_new(MSN_CONTACT_SERVER, - MSN_ADDRESS_BOOK_POST_URL, - MSN_CONTACT_DEL_SOAP_ACTION, - body, - state, - msn_delete_contact_read_cb, - msn_delete_contact_written_cb, - msn_contact_connect_init); + msn_soap_message_send(contact->session, + msn_soap_message_new(MSN_CONTACT_DEL_SOAP_ACTION, + xmlnode_from_str(body, -1)), + MSN_CONTACT_SERVER, MSN_ADDRESS_BOOK_POST_URL, + msn_delete_contact_read_cb, state); g_free(contact_id_xml); - - /* POST the SOAP request */ - msn_soap_post(contact->soapconn, soap_request); - g_free(body); } -static gboolean -msn_del_contact_from_group_read_cb(MsnSoapConn *soapconn) +static void +msn_del_contact_from_group_read_cb(MsnSoapMessage *req, MsnSoapMessage *resp, + gpointer data) { - MsnCallbackState *state = (MsnCallbackState *) soapconn->data_cb; + MsnCallbackState *state = data; - if (soapconn->body == NULL) { - msn_callback_state_free(state); - return TRUE; - } - - if (msn_userlist_rem_buddy_from_group(soapconn->session->userlist, state->who, state->old_group_name)) { - purple_debug_info("MSN CL", "Contact %s deleted successfully from group %s\n", state->who, state->old_group_name); - } else { - purple_debug_info("MSN CL", "Contact %s deleted successfully from group %s in the server, but failed in the local list\n", state->who, state->old_group_name); + if (resp != NULL) { + if (msn_userlist_rem_buddy_from_group(state->session->userlist, + state->who, state->old_group_name)) { + purple_debug_info("MSNCL", "Contact %s deleted successfully from group %s\n", state->who, state->old_group_name); + } else { + purple_debug_info("MSNCL", "Contact %s deleted successfully from group %s in the server, but failed in the local list\n", state->who, state->old_group_name); + } } msn_callback_state_free(state); - msn_soap_free_read_buf(soapconn); - - return TRUE; -} - -static void -msn_del_contact_from_group_written_cb(MsnSoapConn *soapconn) -{ - purple_debug_info("MSN CL","Del contact from group request sent!\n"); - soapconn->read_cb = msn_del_contact_from_group_read_cb; } void msn_del_contact_from_group(MsnContact *contact, const char *passport, const char *group_name) { - MsnSoapReq *soap_request; MsnUserList * userlist; MsnUser *user; MsnCallbackState *state; - gchar *body = NULL, *contact_id_xml; + gchar *body, *contact_id_xml; const gchar *groupId; g_return_if_fail(passport != NULL); @@ -1336,16 +998,16 @@ groupId = msn_userlist_find_group_id(userlist, group_name); if (groupId != NULL) { - purple_debug_info("MSN CL", "Deleting user %s from group %s\n", passport, group_name); + purple_debug_info("MSNCL", "Deleting user %s from group %s\n", passport, group_name); } else { - purple_debug_warning("MSN CL", "Unable to retrieve group id from group %s !\n", group_name); + purple_debug_warning("MSNCL", "Unable to retrieve group id from group %s !\n", group_name); return; } user = msn_userlist_find_user(userlist, passport); if (user == NULL) { - purple_debug_warning("MSN CL", "Unable to retrieve user from passport %s!\n", passport); + purple_debug_warning("MSNCL", "Unable to retrieve user from passport %s!\n", passport); return; } @@ -1354,58 +1016,40 @@ return; } - state = msn_callback_state_new(); + state = msn_callback_state_new(contact->session); msn_callback_state_set_who(state, passport); msn_callback_state_set_guid(state, groupId); msn_callback_state_set_old_group_name(state, group_name); contact_id_xml = g_strdup_printf(MSN_CONTACT_ID_XML, user->uid); body = g_strdup_printf(MSN_CONTACT_DEL_GROUP_TEMPLATE, contact_id_xml, groupId); + + msn_soap_message_send(contact->session, + msn_soap_message_new(MSN_CONTACT_DEL_GROUP_SOAP_ACTION, + xmlnode_from_str(body, -1)), + MSN_CONTACT_SERVER, MSN_ADDRESS_BOOK_POST_URL, + msn_del_contact_from_group_read_cb, state); + g_free(contact_id_xml); - - /*build SOAP and POST it*/ - soap_request = msn_soap_request_new(MSN_CONTACT_SERVER, - MSN_ADDRESS_BOOK_POST_URL, - MSN_CONTACT_DEL_GROUP_SOAP_ACTION, - body, - state, - msn_del_contact_from_group_read_cb, - msn_del_contact_from_group_written_cb, - msn_contact_connect_init); - msn_soap_post(contact->soapconn,soap_request); - g_free(body); } -static gboolean -msn_update_contact_read_cb(MsnSoapConn *soapconn) +static void +msn_update_contact_read_cb(MsnSoapMessage *req, MsnSoapMessage *resp, + gpointer data) { - if (soapconn->body == NULL) - return TRUE; - - purple_debug_info("MSN CL","Contact updated successfully\n"); - - return TRUE; -} - -static void -msn_update_contact_written_cb(MsnSoapConn *soapconn) -{ - purple_debug_info("MSN CL","Update contact information request sent\n"); - soapconn->read_cb = msn_update_contact_read_cb; + if (resp) + purple_debug_info("MSN CL","Contact updated successfully\n"); + else + purple_debug_info("MSN CL","Contact updated successfully\n"); } /* Update a contact's nickname */ - void msn_update_contact(MsnContact *contact, const char* nickname) { - MsnSoapReq *soap_request; - gchar *body, *escaped_nickname; - - /* I'm not sure this is right, but if it isn't, the rest of this function will need to be fixed */ - g_return_if_fail(nickname != NULL); + gchar *body = NULL, *escaped_nickname; purple_debug_info("MSN CL","Update contact information with new friendly name: %s\n", nickname); @@ -1413,77 +1057,50 @@ body = g_strdup_printf(MSN_CONTACT_UPDATE_TEMPLATE, escaped_nickname); + msn_soap_message_send(contact->session, + msn_soap_message_new(MSN_CONTACT_UPDATE_SOAP_ACTION, + xmlnode_from_str(body, -1)), + MSN_CONTACT_SERVER, MSN_ADDRESS_BOOK_POST_URL, + msn_update_contact_read_cb, NULL); + g_free(escaped_nickname); - /*build SOAP and POST it*/ - soap_request = msn_soap_request_new(MSN_CONTACT_SERVER, - MSN_ADDRESS_BOOK_POST_URL, - MSN_CONTACT_UPDATE_SOAP_ACTION, - body, - NULL, - msn_update_contact_read_cb, - msn_update_contact_written_cb, - msn_contact_connect_init); - msn_soap_post(contact->soapconn, soap_request); - g_free(body); } - -static gboolean -msn_del_contact_from_list_read_cb(MsnSoapConn *soapconn) +static void +msn_del_contact_from_list_read_cb(MsnSoapMessage *req, MsnSoapMessage *resp, + gpointer data) { - MsnCallbackState *state = NULL; + MsnCallbackState *state = data; + MsnSession *session = state->session; - g_return_val_if_fail(soapconn->data_cb != NULL, TRUE); - g_return_val_if_fail(soapconn->session != NULL, FALSE); - g_return_val_if_fail(soapconn->session->contact != NULL, FALSE); + if (resp != NULL) { + purple_debug_info("MSN CL", "Contact %s deleted successfully from %s list on server!\n", state->who, MsnMemberRole[state->list_id]); - state = (MsnCallbackState *) soapconn->data_cb; + if (state->list_id == MSN_LIST_PL) { + MsnUser *user = msn_userlist_find_user(session->userlist, state->who); - if (soapconn->body == NULL) { - msn_callback_state_free(state); - return TRUE; - } - - purple_debug_info("MSN CL", "Contact %s deleted successfully from %s list on server!\n", state->who, MsnMemberRole[state->list_id]); - - if (state->list_id == MSN_LIST_PL) { - msn_add_contact_to_list(soapconn->session->contact, state, state->who, MSN_LIST_RL); - return TRUE; - } + if (user != NULL) + msn_user_unset_op(user, MSN_LIST_PL_OP); - if (state->list_id == MSN_LIST_AL) { - purple_privacy_permit_remove(soapconn->session->account, state->who, TRUE); - msn_add_contact_to_list(soapconn->session->contact, NULL, state->who, MSN_LIST_BL); - msn_callback_state_free(state); - return TRUE; - } - - if (state->list_id == MSN_LIST_BL) { - purple_privacy_deny_remove(soapconn->session->account, state->who, TRUE); - msn_add_contact_to_list(soapconn->session->contact, NULL, state->who, MSN_LIST_AL); - msn_callback_state_free(state); - return TRUE; + msn_add_contact_to_list(session->contact, state, state->who, MSN_LIST_RL); + return; + } else if (state->list_id == MSN_LIST_AL) { + purple_privacy_permit_remove(session->account, state->who, TRUE); + msn_add_contact_to_list(session->contact, NULL, state->who, MSN_LIST_BL); + } else if (state->list_id == MSN_LIST_BL) { + purple_privacy_deny_remove(session->account, state->who, TRUE); + msn_add_contact_to_list(session->contact, NULL, state->who, MSN_LIST_AL); + } } msn_callback_state_free(state); - msn_soap_free_read_buf(soapconn); - - return TRUE; -} - -static void -msn_del_contact_from_list_written_cb(MsnSoapConn *soapconn) -{ - purple_debug_info("MSN CL","Delete contact from list SOAP request sent!\n"); - soapconn->read_cb = msn_del_contact_from_list_read_cb; } void msn_del_contact_from_list(MsnContact *contact, MsnCallbackState *state, const gchar *passport, const MsnListId list) { - MsnSoapReq *soap_request; gchar *body = NULL, *member = NULL; MsnSoapPartnerScenario partner_scenario; MsnUser *user; @@ -1495,7 +1112,7 @@ purple_debug_info("MSN CL", "Deleting contact %s from %s list\n", passport, MsnMemberRole[list]); if (state == NULL) { - state = msn_callback_state_new(); + state = msn_callback_state_new(contact->session); } msn_callback_state_set_list_id(state, list); msn_callback_state_set_who(state, passport); @@ -1519,70 +1136,55 @@ MsnSoapPartnerScenarioText[partner_scenario], MsnMemberRole[list], member); - g_free(member); - soap_request = msn_soap_request_new( MSN_CONTACT_SERVER, - MSN_SHARE_POST_URL, - MSN_DELETE_MEMBER_FROM_LIST_SOAP_ACTION, - body, - state, - msn_del_contact_from_list_read_cb, - msn_del_contact_from_list_written_cb, - msn_contact_connect_init); + msn_soap_message_send(contact->session, + msn_soap_message_new(MSN_DELETE_MEMBER_FROM_LIST_SOAP_ACTION, + xmlnode_from_str(body, -1)), + MSN_CONTACT_SERVER, MSN_SHARE_POST_URL, + msn_del_contact_from_list_read_cb, state); - msn_soap_post(contact->soapconn,soap_request); - + g_free(member); g_free(body); } -static gboolean -msn_add_contact_to_list_read_cb(MsnSoapConn *soapconn) +static void +msn_add_contact_to_list_read_cb(MsnSoapMessage *req, MsnSoapMessage *resp, + gpointer data) { - MsnCallbackState *state = NULL; - - g_return_val_if_fail(soapconn->data_cb != NULL, TRUE); + MsnCallbackState *state = data; - state = (MsnCallbackState *) soapconn->data_cb; - - if (soapconn->body == NULL) { - msn_callback_state_free(state); - return TRUE; - } + g_return_if_fail(state != NULL); + g_return_if_fail(state->session != NULL); + g_return_if_fail(state->session->contact != NULL); - purple_debug_info("MSN CL", "Contact %s added successfully to %s list on server!\n", state->who, MsnMemberRole[state->list_id]); + if (resp != NULL) { + purple_debug_info("MSN CL", "Contact %s added successfully to %s list on server!\n", state->who, MsnMemberRole[state->list_id]); - if (state->list_id == MSN_LIST_RL && (state->action & MSN_DENIED_BUDDY) ) { - g_return_val_if_fail(soapconn->session != NULL, FALSE); - g_return_val_if_fail(soapconn->session->contact != NULL, FALSE); + if (state->list_id == MSN_LIST_RL) { + MsnUser *user = msn_userlist_find_user(state->session->userlist, state->who); + + if (user != NULL) { + msn_user_set_op(user, MSN_LIST_RL_OP); + } - msn_add_contact_to_list(soapconn->session->contact, NULL, state->who, MSN_LIST_BL); - return TRUE; - } + if (state->action & MSN_DENIED_BUDDY) { - if (state->list_id == MSN_LIST_AL) { - purple_privacy_permit_add(soapconn->session->account, state->who, TRUE); - } else if (state->list_id == MSN_LIST_BL) { - purple_privacy_deny_add(soapconn->session->account, state->who, TRUE); + msn_add_contact_to_list(state->session->contact, NULL, state->who, MSN_LIST_BL); + } else if (state->list_id == MSN_LIST_AL) { + purple_privacy_permit_add(state->session->account, state->who, TRUE); + } else if (state->list_id == MSN_LIST_BL) { + purple_privacy_deny_add(state->session->account, state->who, TRUE); + } + } } msn_callback_state_free(state); - msn_soap_free_read_buf(soapconn); - return TRUE; -} - - -static void -msn_add_contact_to_list_written_cb(MsnSoapConn *soapconn) -{ - purple_debug_info("MSN CL","Add contact to list SOAP request sent!\n"); - soapconn->read_cb = msn_add_contact_to_list_read_cb; } void msn_add_contact_to_list(MsnContact *contact, MsnCallbackState *state, const gchar *passport, const MsnListId list) { - MsnSoapReq *soap_request; gchar *body = NULL, *member = NULL; MsnSoapPartnerScenario partner_scenario; @@ -1593,51 +1195,38 @@ purple_debug_info("MSN CL", "Adding contact %s to %s list\n", passport, MsnMemberRole[list]); if (state == NULL) { - state = msn_callback_state_new(); + state = msn_callback_state_new(contact->session); } msn_callback_state_set_list_id(state, list); msn_callback_state_set_who(state, passport); partner_scenario = (list == MSN_LIST_RL) ? MSN_PS_CONTACT_API : MSN_PS_BLOCK_UNBLOCK; - member = g_strdup_printf(MSN_MEMBER_PASSPORT_XML, passport); + member = g_strdup_printf(MSN_MEMBER_PASSPORT_XML, state->who); body = g_strdup_printf(MSN_CONTACT_ADD_TO_LIST_TEMPLATE, MsnSoapPartnerScenarioText[partner_scenario], MsnMemberRole[list], member); - g_free(member); + msn_soap_message_send(contact->session, + msn_soap_message_new(MSN_ADD_MEMBER_TO_LIST_SOAP_ACTION, + xmlnode_from_str(body, -1)), + MSN_CONTACT_SERVER, MSN_SHARE_POST_URL, + msn_add_contact_to_list_read_cb, state); - soap_request = msn_soap_request_new( MSN_CONTACT_SERVER, - MSN_SHARE_POST_URL, - MSN_ADD_MEMBER_TO_LIST_SOAP_ACTION, - body, - state, - msn_add_contact_to_list_read_cb, - msn_add_contact_to_list_written_cb, - msn_contact_connect_init); - - msn_soap_post(contact->soapconn, soap_request); - + g_free(member); g_free(body); } - #if 0 -static gboolean -msn_gleams_read_cb(MsnSoapConn * soapconn) +static void +msn_gleams_read_cb(MsnSoapMessage *req, MsnSoapMessage *resp, gpointer data) { - purple_debug_info("MSN CL","Gleams read done\n"); - return TRUE; -} - -static void -msn_gleams_written_cb(MsnSoapConn * soapconn) -{ - purple_debug_info("MSNP14","finish Group written\n"); - soapconn->read_cb = msn_gleams_read_cb; -// msn_soap_read_cb(data,source,cond); + if (resp != NULL) + purple_debug_info("MSNP14","Gleams read done\n"); + else + purple_debug_info("MSNP14","Gleams read failed\n"); } /*get the gleams info*/ @@ -1647,16 +1236,11 @@ MsnSoapReq *soap_request; purple_debug_info("MSNP14","msn get gleams info...\n"); - /*build SOAP and POST it*/ - soap_request = msn_soap_request_new(MSN_CONTACT_SERVER, - MSN_ADDRESS_BOOK_POST_URL, - MSN_GET_GLEAMS_SOAP_ACTION, - MSN_GLEAMS_TEMPLATE, - NULL, - msn_gleams_read_cb, - msn_gleams_written_cb, - msn_contact_connect_init); - msn_soap_post(contact->soapconn,soap_request); + msn_soap_message_send(contact->session, + msn_soap_message_new(MSN_GET_GLEAMS_SOAP_ACTION, + xmlnode_from_str(MSN_GLEAMS_TEMPLATE, -1)), + MSN_CONTACT_SERVER, MSN_ADDRESS_BOOK_POST_URL, + msn_gleams_read_cb, NULL); } #endif @@ -1665,100 +1249,88 @@ * Group Operations ***************************************************************/ -static gboolean -msn_group_read_cb(MsnSoapConn *soapconn) +static void +msn_group_read_cb(MsnSoapMessage *req, MsnSoapMessage *resp, gpointer data) { - MsnUserList *userlist; - MsnCallbackState *state = NULL; + MsnCallbackState *state = data; - purple_debug_info("MSN CL", "Group request successful.\n"); + purple_debug_info("MSNCL", "Group request successful.\n"); - g_return_val_if_fail(soapconn->session != NULL, FALSE); - g_return_val_if_fail(soapconn->session->userlist != NULL, TRUE); - g_return_val_if_fail(soapconn->session->contact != NULL, FALSE); + g_return_if_fail(state->session != NULL); + g_return_if_fail(state->session->userlist != NULL); + g_return_if_fail(state->session->contact != NULL); - state = (MsnCallbackState *) soapconn->data_cb; - - if (soapconn->body == NULL) { + if (resp == NULL) { msn_callback_state_free(state); - return TRUE; + return; } - + if (state) { - userlist = soapconn->session->userlist; + MsnSession *session = state->session; + MsnUserList *userlist = session->userlist; if (state->action & MSN_RENAME_GROUP) { - msn_userlist_rename_group_id(soapconn->session->userlist, + msn_userlist_rename_group_id(session->userlist, state->guid, state->new_group_name); } if (state->action & MSN_ADD_GROUP) { - gchar *guid, *endguid; - - guid = g_strstr_len(soapconn->read_buf, soapconn->read_len, "<guid>"); - guid += 6; - endguid = g_strstr_len(soapconn->read_buf, soapconn->read_len, "</guid>"); - *endguid = '\0'; - /* create and add the new group to the userlist */ - purple_debug_info("MSN CL", "Adding group %s with guid = %s to the userlist\n", state->new_group_name, guid); - msn_group_new(soapconn->session->userlist, guid, state->new_group_name); + /* the response is taken from + http://telepathy.freedesktop.org/wiki/Pymsn/MSNP/ContactListActions + should copy it to msnpiki some day */ + xmlnode *guid_node = msn_soap_xml_get(resp->xml, + "Body/ABGroupAddResponse/ABGroupAddResult/guid"); + + if (guid_node) { + char *guid = xmlnode_get_data(guid_node); + + /* create and add the new group to the userlist */ + purple_debug_info("MSNCL", "Adding group %s with guid = %s to the userlist\n", state->new_group_name, guid); + msn_group_new(session->userlist, guid, state->new_group_name); - if (state->action & MSN_ADD_BUDDY) { - msn_userlist_add_buddy(soapconn->session->userlist, - state->who, - state->new_group_name); - msn_callback_state_free(state); - return TRUE; - } - - if (state->action & MSN_MOVE_BUDDY) { - msn_add_contact_to_group(soapconn->session->contact, state, state->who, guid); - return TRUE; + g_free(guid); + + if (state->action & MSN_ADD_BUDDY) { + msn_userlist_add_buddy(session->userlist, + state->who, + state->new_group_name); + } else if (state->action & MSN_MOVE_BUDDY) { + msn_add_contact_to_group(session->contact, state, state->who, guid); + return; + } + } else { + purple_debug_info("MSNCL", "Adding group %s failed\n", + state->new_group_name); } } if (state->action & MSN_DEL_GROUP) { GList *l; - msn_userlist_remove_group_id(soapconn->session->userlist, state->guid); + msn_userlist_remove_group_id(session->userlist, state->guid); for (l = userlist->users; l != NULL; l = l->next) { msn_user_remove_group_id( (MsnUser *)l->data, state->guid); } - } msn_callback_state_free(state); } - - msn_soap_free_read_buf(soapconn); - return TRUE; -} - -static void -msn_group_written_cb(MsnSoapConn *soapconn) -{ - purple_debug_info("MSN CL","Sent group request.\n"); - soapconn->read_cb = msn_group_read_cb; } /* add group */ void msn_add_group(MsnSession *session, MsnCallbackState *state, const char* group_name) { - MsnSoapReq *soap_request; - MsnContact *contact; char *body = NULL; - gchar *escaped_group_name; g_return_if_fail(session != NULL); g_return_if_fail(group_name != NULL); - contact = session->contact; - purple_debug_info("MSN CL","Adding group %s to contact list.\n", group_name); + purple_debug_info("MSNCL","Adding group %s to contact list.\n", group_name); if (state == NULL) { - state = msn_callback_state_new(); + state = msn_callback_state_new(session); } msn_callback_state_set_action(state, MSN_ADD_GROUP); @@ -1767,21 +1339,14 @@ /* escape group name's html special chars so it can safely be sent * in a XML SOAP request */ - escaped_group_name = g_markup_escape_text(group_name, -1); - body = g_strdup_printf(MSN_GROUP_ADD_TEMPLATE, escaped_group_name); - g_free(escaped_group_name); + body = g_markup_printf_escaped(MSN_GROUP_ADD_TEMPLATE, group_name); - /*build SOAP and POST it*/ - soap_request = msn_soap_request_new(MSN_CONTACT_SERVER, - MSN_ADDRESS_BOOK_POST_URL, - MSN_GROUP_ADD_SOAP_ACTION, - body, - state, - msn_group_read_cb, - msn_group_written_cb, - msn_contact_connect_init); - msn_soap_post(contact->soapconn,soap_request); - + msn_soap_message_send(session, + msn_soap_message_new(MSN_GROUP_ADD_SOAP_ACTION, + xmlnode_from_str(body, -1)), + MSN_CONTACT_SERVER, MSN_ADDRESS_BOOK_POST_URL, + msn_group_read_cb, state); + g_free(body); } @@ -1789,8 +1354,6 @@ void msn_del_group(MsnSession *session, const gchar *group_name) { - MsnSoapReq *soap_request; - MsnContact *contact; MsnCallbackState *state; char *body = NULL; const gchar *guid; @@ -1798,8 +1361,7 @@ g_return_if_fail(session != NULL); g_return_if_fail(group_name != NULL); - contact = session->contact; - purple_debug_info("MSN CL","Deleting group %s from contact list\n", group_name); + purple_debug_info("MSNCL","Deleting group %s from contact list\n", group_name); guid = msn_userlist_find_group_id(session->userlist, group_name); @@ -1807,7 +1369,7 @@ * we need to delete nothing */ if (guid == NULL) { - purple_debug_info("MSN CL", "Group %s guid not found, returning.\n", group_name); + purple_debug_info("MSNCL", "Group %s guid not found, returning.\n", group_name); return; } @@ -1816,21 +1378,17 @@ return; } - state = msn_callback_state_new(); + state = msn_callback_state_new(session); msn_callback_state_set_action(state, MSN_DEL_GROUP); msn_callback_state_set_guid(state, guid); body = g_strdup_printf(MSN_GROUP_DEL_TEMPLATE, guid); - /*build SOAP and POST it*/ - soap_request = msn_soap_request_new(MSN_CONTACT_SERVER, - MSN_ADDRESS_BOOK_POST_URL, - MSN_GROUP_DEL_SOAP_ACTION, - body, - state, - msn_group_read_cb, - msn_group_written_cb, - msn_contact_connect_init); - msn_soap_post(contact->soapconn, soap_request); + + msn_soap_message_send(session, + msn_soap_message_new(MSN_GROUP_DEL_SOAP_ACTION, + xmlnode_from_str(body, -1)), + MSN_CONTACT_SERVER, MSN_ADDRESS_BOOK_POST_URL, + msn_group_read_cb, state); g_free(body); } @@ -1839,24 +1397,22 @@ void msn_contact_rename_group(MsnSession *session, const char *old_group_name, const char *new_group_name) { - MsnSoapReq *soap_request; - MsnContact *contact; - gchar * escaped_group_name, *body = NULL; + gchar *body = NULL; const gchar * guid; - MsnCallbackState *state = msn_callback_state_new(); + MsnCallbackState *state; g_return_if_fail(session != NULL); g_return_if_fail(session->userlist != NULL); g_return_if_fail(old_group_name != NULL); g_return_if_fail(new_group_name != NULL); - contact = session->contact; purple_debug_info("MSN CL", "Renaming group %s to %s.\n", old_group_name, new_group_name); guid = msn_userlist_find_group_id(session->userlist, old_group_name); if (guid == NULL) return; + state = msn_callback_state_new(session); msn_callback_state_set_guid(state, guid); msn_callback_state_set_new_group_name(state, new_group_name); @@ -1867,31 +1423,14 @@ msn_callback_state_set_action(state, MSN_RENAME_GROUP); - /* escape group name's html special chars so it can safely be sent - * in a XML SOAP request - */ - escaped_group_name = g_markup_escape_text(new_group_name, -1); - - body = g_strdup_printf(MSN_GROUP_RENAME_TEMPLATE, guid, escaped_group_name); + body = g_markup_printf_escaped(MSN_GROUP_RENAME_TEMPLATE, + guid, new_group_name); - soap_request = msn_soap_request_new(MSN_CONTACT_SERVER, - MSN_ADDRESS_BOOK_POST_URL, - MSN_GROUP_RENAME_SOAP_ACTION, - body, - state, - msn_group_read_cb, - msn_group_written_cb, - msn_contact_connect_init); - msn_soap_post(contact->soapconn, soap_request); - - g_free(escaped_group_name); + msn_soap_message_send(session, + msn_soap_message_new(MSN_GROUP_RENAME_SOAP_ACTION, + xmlnode_from_str(body, -1)), + MSN_CONTACT_SERVER, MSN_ADDRESS_BOOK_POST_URL, + msn_group_read_cb, state); + g_free(body); } - -void -msn_contact_connect_init(MsnSoapConn *soapconn) -{ - msn_soap_init(soapconn, MSN_CONTACT_SERVER, 1, - msn_contact_login_connect_cb, - msn_contact_login_error_cb); -}
--- a/libpurple/protocols/msn/contact.h Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/msn/contact.h Sat Nov 17 02:20:01 2007 +0000 @@ -380,6 +380,7 @@ gchar * guid; MsnListId list_id; MsnCallbackAction action; + MsnSession *session; }; typedef enum @@ -397,7 +398,7 @@ MsnContact * msn_contact_new(MsnSession *session); void msn_contact_destroy(MsnContact *contact); -MsnCallbackState * msn_callback_state_new(void); +MsnCallbackState * msn_callback_state_new(MsnSession *session); void msn_callback_state_free(MsnCallbackState *state); void msn_callback_state_set_who(MsnCallbackState *state, const gchar *who); void msn_callback_state_set_uid(MsnCallbackState *state, const gchar *uid);
--- a/libpurple/protocols/msn/directconn.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/msn/directconn.c Sat Nov 17 02:20:01 2007 +0000 @@ -80,6 +80,7 @@ create_listener(int port) { int fd; + int flags; const int on = 1; #if 0 @@ -155,7 +156,8 @@ return -1; } - fcntl(fd, F_SETFL, O_NONBLOCK); + flags = fcntl(fd, F_GETFL); + fcntl(fd, F_SETFL, flags | O_NONBLOCK); return fd; }
--- a/libpurple/protocols/msn/group.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/msn/group.c Sat Nov 17 02:20:01 2007 +0000 @@ -58,6 +58,7 @@ g_return_if_fail(group != NULL); g_return_if_fail(id != NULL); + g_free(group->id); group->id = g_strdup(id); } @@ -67,9 +68,7 @@ g_return_if_fail(group != NULL); g_return_if_fail(name != NULL); - if (group->name != NULL) - g_free(group->name); - + g_free(group->name); group->name = g_strdup(name); }
--- a/libpurple/protocols/msn/httpconn.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/msn/httpconn.c Sat Nov 17 02:20:01 2007 +0000 @@ -169,7 +169,7 @@ /* Now we should be able to process the data. */ if ((s = purple_strcasestr(header, "X-MSN-Messenger: ")) != NULL) { - char *full_session_id, *gw_ip, *session_action; + gchar *full_session_id = NULL, *gw_ip = NULL, *session_action = NULL; char *t, *session_id; char **elems, **cur, **tokens; @@ -196,13 +196,16 @@ { tokens = g_strsplit(*cur, "=", 2); - if (strcmp(tokens[0], "SessionID") == 0) + if (strcmp(tokens[0], "SessionID") == 0) { + g_free(full_session_id); full_session_id = tokens[1]; - else if (strcmp(tokens[0], "GW-IP") == 0) + } else if (strcmp(tokens[0], "GW-IP") == 0) { + g_free(gw_ip); gw_ip = tokens[1]; - else if (strcmp(tokens[0], "Session") == 0) + } else if (strcmp(tokens[0], "Session") == 0) { + g_free(session_action); session_action = tokens[1]; - else + } else g_free(tokens[1]); g_free(tokens[0]); @@ -684,6 +687,17 @@ g_free(httpconn->host); + while (httpconn->queue != NULL) { + MsnHttpQueueData *queue_data; + + queue_data = (MsnHttpQueueData *) httpconn->queue->data; + + httpconn->queue = g_list_delete_link(httpconn->queue, httpconn->queue); + + g_free(queue_data->body); + g_free(queue_data); + } + purple_circ_buffer_destroy(httpconn->tx_buf); if (httpconn->tx_handler > 0) purple_input_remove(httpconn->tx_handler);
--- a/libpurple/protocols/msn/msg.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/msn/msg.c Sat Nov 17 02:20:01 2007 +0000 @@ -60,17 +60,10 @@ purple_debug_info("msn", "message destroy (%p)\n", msg); #endif - if (msg->remote_user != NULL) - g_free(msg->remote_user); - - if (msg->body != NULL) - g_free(msg->body); - - if (msg->content_type != NULL) - g_free(msg->content_type); - - if (msg->charset != NULL) - g_free(msg->charset); + g_free(msg->remote_user); + g_free(msg->body); + g_free(msg->content_type); + g_free(msg->charset); g_hash_table_destroy(msg->attr_table); g_list_free(msg->attr_list); @@ -313,6 +306,7 @@ /* Import the body. */ if (body_len > 0) { msg->body_len = body_len; + g_free(msg->body); msg->body = g_malloc0(msg->body_len + 1); memcpy(msg->body, tmp, msg->body_len); tmp += body_len; @@ -329,6 +323,7 @@ { if (payload_len - (tmp - tmp_base) > 0) { msg->body_len = payload_len - (tmp - tmp_base); + g_free(msg->body); msg->body = g_malloc0(msg->body_len + 1); memcpy(msg->body, tmp, msg->body_len); } @@ -554,10 +549,8 @@ { g_return_if_fail(msg != NULL); - if (msg->content_type != NULL) - g_free(msg->content_type); - - msg->content_type = (type != NULL) ? g_strdup(type) : NULL; + g_free(msg->content_type); + msg->content_type = g_strdup(type); } const char * @@ -573,10 +566,8 @@ { g_return_if_fail(msg != NULL); - if (msg->charset != NULL) - g_free(msg->charset); - - msg->charset = (charset != NULL) ? g_strdup(charset) : NULL; + g_free(msg->charset); + msg->charset = g_strdup(charset); } const char * @@ -664,10 +655,11 @@ tokens = g_strsplit(*cur, ": ", 2); - if (tokens[0] != NULL && tokens[1] != NULL) + if (tokens[0] != NULL && tokens[1] != NULL) { g_hash_table_insert(table, tokens[0], tokens[1]); - - g_free(tokens); + g_free(tokens); + } else + g_strfreev(tokens); } g_strfreev(elems);
--- a/libpurple/protocols/msn/msn.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/msn/msn.c Sat Nov 17 02:20:01 2007 +0000 @@ -254,6 +254,7 @@ trans = msn_transaction_new(cmdproc, "PGD", "%s 1 %d", who, payload_len); msn_transaction_set_payload(trans, payload, payload_len); + g_free(payload); msn_page_destroy(page); @@ -539,25 +540,36 @@ /* * Set the User status text - * Add the PSM String Using "Name - PSM String" format */ static char * msn_status_text(PurpleBuddy *buddy) { PurplePresence *presence; PurpleStatus *status; - const char *msg, *cmedia; + const char *msg; presence = purple_buddy_get_presence(buddy); status = purple_presence_get_active_status(presence); + /* I think status message should take precedence over media */ msg = purple_status_get_attr_string(status, "message"); - cmedia = purple_status_get_attr_string(status, "currentmedia"); - - if (cmedia) - return g_markup_escape_text(cmedia, -1); - else if (msg) + if (msg && *msg) return g_markup_escape_text(msg, -1); + + if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_TUNE)) { + const char *title, *artist; + char *media, *esc; + status = purple_presence_get_status(presence, "tune"); + title = purple_status_get_attr_string(status, PURPLE_TUNE_TITLE); + artist = purple_status_get_attr_string(status, PURPLE_TUNE_ARTIST); + + media = g_strdup_printf("%s%s%s", title, artist ? " - " : "", + artist ? artist : ""); + esc = g_markup_escape_text(media, -1); + g_free(media); + return esc; + } + return NULL; } @@ -570,23 +582,36 @@ user = buddy->proto_data; - if (purple_presence_is_online(presence)) { - const char *psm, *currentmedia, *name; + const char *psm, *name; + char *currentmedia = NULL; char *tmp; psm = purple_status_get_attr_string(status, "message"); - currentmedia = purple_status_get_attr_string(status, "currentmedia"); - - if (!purple_presence_is_available(presence)) { + if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_TUNE)) { + PurpleStatus *tune = purple_presence_get_status(presence, "tune"); + const char *title = purple_status_get_attr_string(tune, PURPLE_TUNE_TITLE); + const char *artist = purple_status_get_attr_string(tune, PURPLE_TUNE_ARTIST); + currentmedia = g_strdup_printf("%s%s%s", title, artist ? " - " : "", + artist ? artist : ""); + /* We could probably just use user->media.title etc. here */ + } + + if (!purple_status_is_available(status)) { name = purple_status_get_name(status); } else { name = NULL; } if (name != NULL && *name) { - char *tmp2 = g_markup_escape_text(name, -1); + char *tmp2; + + if (purple_presence_is_idle(presence)) { + tmp2 = g_markup_printf_escaped("%s/%s", name, _("Idle")); + } else { + tmp2 = g_markup_escape_text(name, -1); + } if (psm != NULL && *psm) { tmp = g_markup_escape_text(psm, -1); @@ -600,8 +625,20 @@ } else { if (psm != NULL && *psm) { tmp = g_markup_escape_text(psm, -1); - purple_notify_user_info_add_pair(user_info, _("Status"), tmp); + if (purple_presence_is_idle(presence)) { + purple_notify_user_info_add_pair(user_info, _("Idle"), tmp); + } else { + purple_notify_user_info_add_pair(user_info, _("Status"), tmp); + } g_free(tmp); + } else { + if (purple_presence_is_idle(presence)) { + purple_notify_user_info_add_pair(user_info, _("Status"), + _("Idle")); + } else { + purple_notify_user_info_add_pair(user_info, _("Status"), + purple_status_get_name(status)); + } } } @@ -609,6 +646,7 @@ tmp = g_markup_escape_text(currentmedia, -1); purple_notify_user_info_add_pair(user_info, _("Current media"), tmp); g_free(tmp); + g_free(currentmedia); } } @@ -616,7 +654,12 @@ * XXX: blocked icon overlay isn't always accurate for MSN. * XXX: This can die as soon as purple_privacy_check() knows that * XXX: this prpl always honors both the allow and deny lists. */ - if (user) + /* While the above comment may be strictly correct (the privacy API needs + * rewriteing), purple_privacy_check() is going to be more accurate at + * indicating whether a particular buddy is going to be able to message + * you, which is the important information that this is trying to convey. + */ + if (full && user) { purple_notify_user_info_add_pair(user_info, _("Blocked"), ((user->list_op & (1 << MSN_LIST_BL)) ? _("Yes") : _("No"))); @@ -632,40 +675,34 @@ status = purple_status_type_new_with_attrs( PURPLE_STATUS_AVAILABLE, NULL, NULL, TRUE, TRUE, FALSE, "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING), - "currentmedia", _("Current media"), purple_value_new(PURPLE_TYPE_STRING), NULL); types = g_list_append(types, status); status = purple_status_type_new_with_attrs( PURPLE_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE, "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING), - "currentmedia", _("Current media"), purple_value_new(PURPLE_TYPE_STRING), NULL); types = g_list_append(types, status); status = purple_status_type_new_with_attrs( PURPLE_STATUS_AWAY, "brb", _("Be Right Back"), TRUE, TRUE, FALSE, "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING), - "currentmedia", _("Current media"), purple_value_new(PURPLE_TYPE_STRING), NULL); types = g_list_append(types, status); status = purple_status_type_new_with_attrs( PURPLE_STATUS_UNAVAILABLE, "busy", _("Busy"), TRUE, TRUE, FALSE, "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING), - "currentmedia", _("Current media"), purple_value_new(PURPLE_TYPE_STRING), NULL); types = g_list_append(types, status); status = purple_status_type_new_with_attrs( PURPLE_STATUS_UNAVAILABLE, "phone", _("On the Phone"), TRUE, TRUE, FALSE, "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING), - "currentmedia", _("Current media"), purple_value_new(PURPLE_TYPE_STRING), NULL); types = g_list_append(types, status); status = purple_status_type_new_with_attrs( PURPLE_STATUS_AWAY, "lunch", _("Out to Lunch"), TRUE, TRUE, FALSE, "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING), - "currentmedia", _("Current media"), purple_value_new(PURPLE_TYPE_STRING), NULL); types = g_list_append(types, status); @@ -681,6 +718,14 @@ "mobile", NULL, FALSE, FALSE, TRUE); types = g_list_append(types, status); + status = purple_status_type_new_with_attrs(PURPLE_STATUS_TUNE, + "tune", NULL, TRUE, TRUE, TRUE, + PURPLE_TUNE_ARTIST, _("Artist"), purple_value_new(PURPLE_TYPE_STRING), + PURPLE_TUNE_ALBUM, _("Album"), purple_value_new(PURPLE_TYPE_STRING), + PURPLE_TUNE_TITLE, _("Title"), purple_value_new(PURPLE_TYPE_STRING), + NULL); + types = g_list_append(types, status); + return types; } @@ -799,8 +844,8 @@ if (!purple_ssl_is_supported()) { - gc->wants_to_die = TRUE; - purple_connection_error(gc, + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, _("SSL support is needed for MSN. Please install a supported " "SSL library.")); return; @@ -829,7 +874,9 @@ purple_account_set_username(account, username); if (!msn_session_connect(session, host, port, http_method)) - purple_connection_error(gc, _("Failed to connect to server.")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Failed to connect to server.")); } static void @@ -943,27 +990,24 @@ imdata->msg = body_str; imdata->flags = flags; imdata->when = time(NULL); - g_idle_add(msn_send_me_im, imdata); + purple_timeout_add(0, msn_send_me_im, imdata); } msn_message_destroy(msg); }else { /*send Offline Instant Message,only to MSN Passport User*/ MsnSession *session; - MsnOim *oim; char *friendname; purple_debug_info("MSNP14","prepare to send offline Message\n"); session = gc->proto_data; - /* XXX/khc: hack */ - if (!session->oim) - session->oim = msn_oim_new(session); - - oim = session->oim; + friendname = msn_encode_mime(account->username); - msn_oim_prep_send_msg_info(oim, purple_account_get_username(account), - friendname, who, message); - msn_oim_send_msg(oim); + msn_oim_prep_send_msg_info(session->oim, + purple_account_get_username(account), + friendname, who, message); + msn_oim_send_msg(session->oim); + g_free(friendname); } return 1; @@ -1103,7 +1147,7 @@ userlist = session->userlist; who = msn_normalize(gc->account, buddy->name); - purple_debug_info("MSN","Add user:%s to group:%s\n", who, group->name); + purple_debug_info("MSN","Add user:%s to group:%s\n", who, (group && group->name) ? group->name : "(null)"); if (!session->logged_in) { #if 0 @@ -1594,7 +1638,6 @@ gboolean sect_info = FALSE; gboolean has_contact_info = FALSE; char *url_buffer; - GString *s, *s2; int stripped_len; #if PHOTO_SUPPORT char *photo_url_text = NULL; @@ -1679,11 +1722,6 @@ purple_debug_misc("msn", "stripped = %p\n", stripped); purple_debug_misc("msn", "url_buffer = %p\n", url_buffer); - /* Gonna re-use the memory we've already got for url_buffer */ - /* No we're not. */ - s = g_string_sized_new(strlen(url_buffer)); - s2 = g_string_sized_new(strlen(url_buffer)); - /* General section header */ if (has_tooltip_text) purple_notify_user_info_add_section_break(user_info); @@ -1985,10 +2023,10 @@ #if PHOTO_SUPPORT /* Find the URL to the photo; must be before the marshalling [Bug 994207] */ photo_url_text = msn_get_photo_url(url_text); - purple_debug_info("MSNP14","photo url:{%s}\n",photo_url_text); + purple_debug_info("MSNP14","photo url:{%s}\n", photo_url_text ? photo_url_text : "(null)"); /* Marshall the existing state */ - info2_data = g_malloc0(sizeof(MsnGetInfoStepTwoData)); + info2_data = g_new0(MsnGetInfoStepTwoData, 1); info2_data->info_data = info_data; info2_data->stripped = stripped; info2_data->url_buffer = url_buffer; @@ -2030,7 +2068,7 @@ purple_debug_warning("msn", "invalid connection. ignoring buddy photo info.\n"); g_free(stripped); g_free(url_buffer); - g_free(user_info); + purple_notify_user_info_destroy(user_info); g_free(info_data->name); g_free(info_data); g_free(photo_url_text); @@ -2264,7 +2302,7 @@ "prpl-msn", /**< id */ "MSN", /**< name */ - VERSION, /**< version */ + DISPLAY_VERSION, /**< version */ /** summary */ N_("Windows Live Messenger Protocol Plugin"), /** description */
--- a/libpurple/protocols/msn/msnutils.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/msn/msnutils.c Sat Nov 17 02:20:01 2007 +0000 @@ -166,10 +166,15 @@ char * msn_encode_mime(const char *str) { - char *base64; + gchar *base64, *retval; + + g_return_val_if_fail(str != NULL, NULL); base64 = purple_base64_encode((guchar *)str, strlen(str)); - return g_strdup_printf("=?utf-8?B?%s?=", base64); + retval = g_strdup_printf("=?utf-8?B?%s?=", base64); + g_free(base64); + + return retval; } /* @@ -445,10 +450,9 @@ *attributes = g_strdup_printf("FN=%s; EF=%s; CO=%s; PF=0; RL=%c", encode_spaces(fontface), fonteffect, fontcolor, direction); - *message = g_strdup(msg); + *message = msg; g_free(fontface); - g_free(msg); } void
--- a/libpurple/protocols/msn/nexus.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/msn/nexus.c Sat Nov 17 02:20:01 2007 +0000 @@ -22,15 +22,12 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "msn.h" -#include "soap.h" +#include "soap2.h" #include "nexus.h" #include "notification.h" #undef NEXUS_LOGIN_TWN -/*Local Function Prototype*/ -static gboolean nexus_login_connect_cb(MsnSoapConn *soapconn, PurpleSslConnection *gsc); - /************************************************************************** * Main **************************************************************************/ @@ -42,8 +39,6 @@ nexus = g_new0(MsnNexus, 1); nexus->session = session; - /*we must use SSL connection to do Windows Live ID authentication*/ - nexus->soapconn = msn_soap_new(session,nexus,1); nexus->challenge_data = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); @@ -57,201 +52,103 @@ if (nexus->challenge_data != NULL) g_hash_table_destroy(nexus->challenge_data); - msn_soap_destroy(nexus->soapconn); g_free(nexus); } -#if 0 /* khc */ -/************************************************************************** - * Util - **************************************************************************/ - -static gssize -msn_ssl_read(MsnNexus *nexus) -{ - gssize len; - char temp_buf[4096]; - - if ((len = purple_ssl_read(nexus->gsc, temp_buf, - sizeof(temp_buf))) > 0) - { - nexus->read_buf = g_realloc(nexus->read_buf, - nexus->read_len + len + 1); - strncpy(nexus->read_buf + nexus->read_len, temp_buf, len); - nexus->read_len += len; - nexus->read_buf[nexus->read_len] = '\0'; - } - - return len; -} - -static void -nexus_write_cb(gpointer data, gint source, PurpleInputCondition cond) -{ - MsnNexus *nexus = data; - int len, total_len; - - total_len = strlen(nexus->write_buf); - - len = purple_ssl_write(nexus->gsc, - nexus->write_buf + nexus->written_len, - total_len - nexus->written_len); - - if (len < 0 && errno == EAGAIN) - return; - else if (len <= 0) { - purple_input_remove(nexus->input_handler); - nexus->input_handler = 0; - /* TODO: notify of the error */ - return; - } - nexus->written_len += len; - - if (nexus->written_len < total_len) - return; - - purple_input_remove(nexus->input_handler); - nexus->input_handler = 0; - - g_free(nexus->write_buf); - nexus->write_buf = NULL; - nexus->written_len = 0; - - nexus->written_cb(nexus, source, 0); -} - -#endif /************************************************************************** * Login **************************************************************************/ + static void -nexus_login_error_cb(MsnSoapConn *soapconn, PurpleSslConnection *gsc, PurpleSslErrorType error) +nexus_got_response_cb(MsnSoapMessage *req, MsnSoapMessage *resp, gpointer data) { - MsnSession *session; + MsnNexus *nexus = data; + MsnSession *session = nexus->session; + xmlnode *node; + + if (resp == NULL) { + msn_session_set_error(session, MSN_ERROR_AUTH, _("Windows Live ID authentication:Unable to connect")); + return; + } + + node = msn_soap_xml_get(resp->xml, "Body/" + "RequestSecurityTokenResponseCollection/RequestSecurityTokenResponse"); + + for (; node; node = node->next) { + xmlnode *token = msn_soap_xml_get(node, + "RequestedSecurityToken/BinarySecurityToken"); + + if (token) { + char *token_str = xmlnode_get_data(token); + char **elems, **cur, **tokens; + char *msn_twn_t, *msn_twn_p, *cert_str; + + if (token_str == NULL) continue; + + elems = g_strsplit(token_str, "&", 0); - session = soapconn->session; - g_return_if_fail(session != NULL); + for (cur = elems; *cur != NULL; cur++){ + tokens = g_strsplit(*cur, "=", 2); + g_hash_table_insert(nexus->challenge_data, tokens[0], tokens[1]); + /* Don't free each of the tokens, only the array. */ + g_free(tokens); + } + + g_free(token_str); + g_strfreev(elems); + + msn_twn_t = g_hash_table_lookup(nexus->challenge_data, "t"); + msn_twn_p = g_hash_table_lookup(nexus->challenge_data, "p"); + + /*setup the t and p parameter for session*/ + if (session->passport_info.t != NULL){ + g_free(session->passport_info.t); + } + session->passport_info.t = g_strdup(msn_twn_t); - soapconn->gsc = NULL; + if (session->passport_info.p != NULL) + g_free(session->passport_info.p); + session->passport_info.p = g_strdup(msn_twn_p); + + cert_str = g_strdup_printf("t=%s&p=%s",msn_twn_t,msn_twn_p); + msn_got_login_params(session, cert_str); - msn_session_set_error(session, MSN_ERROR_AUTH, _("Windows Live ID authentication:Unable to connect")); - /* the above line will result in nexus being destroyed, so we don't want - * to destroy it here, or we'd crash */ + purple_debug_info("MSN Nexus","Close nexus connection!\n"); + g_free(cert_str); + msn_nexus_destroy(nexus); + session->nexus = NULL; + + return; + } + } + + /* we must have failed! */ + msn_session_set_error(session, MSN_ERROR_AUTH, _("Windows Live ID authentication: cannot find authenticate token in server response")); } -/*process the SOAP reply, get the Authentication Info*/ -static gboolean -nexus_login_read_cb(MsnSoapConn *soapconn) +/*when connect, do the SOAP Style windows Live ID authentication */ +void +msn_nexus_connect(MsnNexus *nexus) { - MsnNexus *nexus; - MsnSession *session; - - char *base, *c; - char *msn_twn_t,*msn_twn_p; - char *login_params; - char **elems, **cur, **tokens; - char * cert_str; - - nexus = soapconn->parent; - g_return_val_if_fail(nexus != NULL, TRUE); - session = nexus->session; - g_return_val_if_fail(session != NULL, FALSE); - - /*reply OK, we should process the SOAP body*/ - purple_debug_info("MSN Nexus","TWN Server Windows Live ID Reply OK!\n"); - - //TODO: we should parse it using XML -#ifdef NEXUS_LOGIN_TWN - base = g_strstr_len(soapconn->read_buf, soapconn->read_len, TWN_START_TOKEN); - base += strlen(TWN_START_TOKEN); - c = g_strstr_len(soapconn->read_buf, soapconn->read_len, TWN_END_TOKEN); -#else - base = g_strstr_len(soapconn->read_buf, soapconn->read_len, TWN_LIVE_START_TOKEN); - base += strlen(TWN_LIVE_START_TOKEN); - c = g_strstr_len(soapconn->read_buf, soapconn->read_len, TWN_LIVE_END_TOKEN); -#endif - login_params = g_strndup(base, c - base); - - // purple_debug_info("msn", "TWN Cert: {%s}\n", login_params); - - /* Parse the challenge data. */ - elems = g_strsplit(login_params, "&", 0); - - for (cur = elems; *cur != NULL; cur++){ - tokens = g_strsplit(*cur, "=", 2); - g_hash_table_insert(nexus->challenge_data, tokens[0], tokens[1]); - /* Don't free each of the tokens, only the array. */ - g_free(tokens); - } - - g_strfreev(elems); - - msn_twn_t = (char *)g_hash_table_lookup(nexus->challenge_data, "t"); - msn_twn_p = (char *)g_hash_table_lookup(nexus->challenge_data, "p"); - - /*setup the t and p parameter for session*/ - if (session->passport_info.t != NULL){ - g_free(session->passport_info.t); - } - session->passport_info.t = g_strdup(msn_twn_t); - - if (session->passport_info.p != NULL) - g_free(session->passport_info.p); - session->passport_info.p = g_strdup(msn_twn_p); - - cert_str = g_strdup_printf("t=%s&p=%s",msn_twn_t,msn_twn_p); - msn_got_login_params(session, cert_str); - - purple_debug_info("MSN Nexus","Close nexus connection!\n"); - g_free(cert_str); - g_free(login_params); - msn_nexus_destroy(nexus); - session->nexus = NULL; - - return FALSE; -} - -static void -nexus_login_written_cb(MsnSoapConn *soapconn) -{ - soapconn->read_cb = nexus_login_read_cb; -// msn_soap_read_cb(data,source,cond); -} - - -/*when connect, do the SOAP Style windows Live ID authentication */ -gboolean -nexus_login_connect_cb(MsnSoapConn *soapconn, PurpleSslConnection *gsc) -{ - MsnNexus * nexus; - MsnSession *session; + MsnSession *session = nexus->session; char *ru,*lc,*id,*tw,*ct,*kpp,*kv,*ver,*rn,*tpf; char *fs0,*fs; char *username, *password; - char *request_str, *head, *tail; + char *tail; #ifdef NEXUS_LOGIN_TWN char *challenge_str; #else char *rst1_str,*rst2_str,*rst3_str; #endif - - purple_debug_info("MSN Nexus","Starting Windows Live ID authentication\n"); - - g_return_val_if_fail(soapconn != NULL, FALSE); - nexus = soapconn->parent; - g_return_val_if_fail(nexus != NULL, TRUE); + MsnSoapMessage *soap; - session = soapconn->session; - g_return_val_if_fail(session != NULL, FALSE); - - msn_soap_set_process_step(soapconn, MSN_SOAP_PROCESSING); - + purple_debug_info("MSN Nexus","Starting Windows Live ID authentication\n"); msn_session_set_login_step(session, MSN_LOGIN_STEP_GET_COOKIE); /*prepare the Windows Live ID authentication token*/ username = g_strdup(purple_account_get_username(session->account)); - password = g_strdup(purple_connection_get_password(session->account->gc)); + password = g_strndup(purple_connection_get_password(session->account->gc), 16); lc = (char *)g_hash_table_lookup(nexus->challenge_data, "lc"); id = (char *)g_hash_table_lookup(nexus->challenge_data, "id"); @@ -275,10 +172,9 @@ msn_session_set_error(session, MSN_ERROR_AUTH, _("Windows Live ID authentication Failed")); g_free(username); g_free(password); - purple_ssl_close(gsc); msn_nexus_destroy(nexus); session->nexus = NULL; - return FALSE; + return; } /* @@ -298,7 +194,7 @@ ); /*build the SOAP windows Live ID XML body */ - tail = g_strdup_printf(TWN_ENVELOP_TEMPLATE,username,password,challenge_str ); + tail = g_strdup_printf(TWN_ENVELOP_TEMPLATE, username, password, challenge_str); g_free(challenge_str); #else rst1_str = g_strdup_printf( @@ -316,153 +212,11 @@ g_free(rst3_str); #endif g_free(fs); - - soapconn->login_path = g_strdup(TWN_POST_URL); - head = g_strdup_printf( - "POST %s HTTP/1.1\r\n" - "Accept: text/*\r\n" - "User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)\r\n" - "Host: %s\r\n" - "Content-Length: %" G_GSIZE_FORMAT "\r\n" - "Connection: Keep-Alive\r\n" - "Cache-Control: no-cache\r\n\r\n", - soapconn->login_path, soapconn->login_host, strlen(tail)); - - request_str = g_strdup_printf("%s%s", head,tail); - -#ifdef MSN_SOAP_DEBUG - purple_debug_misc("MSN Nexus", "TWN Sending:\n%s\n", request_str); -#endif - g_free(head); - g_free(tail); - g_free(username); g_free(password); - /*prepare to send the SOAP request*/ - msn_soap_write(soapconn, request_str, nexus_login_written_cb); - - return TRUE; + soap = msn_soap_message_new(NULL, xmlnode_from_str(tail, -1)); + g_free(tail); + msn_soap_message_send(nexus->session, soap, MSN_TWN_SERVER, TWN_POST_URL, + nexus_got_response_cb, nexus); } -#if 0 /* khc */ -static void -nexus_connect_written_cb(gpointer data, gint source, PurpleInputCondition cond) -{ - MsnNexus *nexus = data; - int len; - - char *da_login; - char *base, *c; - - if (nexus->input_handler == 0) - /* TODO: Use purple_ssl_input_add()? */ - nexus->input_handler = purple_input_add(nexus->gsc->fd, - PURPLE_INPUT_READ, nexus_connect_written_cb, nexus); - - - /* Get the PassportURLs line. */ - len = msn_ssl_read(nexus); - - if (len < 0 && errno == EAGAIN) - return; - else if (len < 0) { - purple_input_remove(nexus->input_handler); - nexus->input_handler = 0; - g_free(nexus->read_buf); - nexus->read_buf = NULL; - nexus->read_len = 0; - /* TODO: error handling */ - return; - } - - if (g_strstr_len(nexus->read_buf, nexus->read_len, - "\r\n\r\n") == NULL) - return; - - purple_input_remove(nexus->input_handler); - nexus->input_handler = 0; - - base = strstr(nexus->read_buf, "PassportURLs"); - - if (base == NULL) - { - g_free(nexus->read_buf); - nexus->read_buf = NULL; - nexus->read_len = 0; - return; - } - - if ((da_login = strstr(base, "DALogin=")) != NULL) - { - /* skip over "DALogin=" */ - da_login += 8; - - if ((c = strchr(da_login, ',')) != NULL) - *c = '\0'; - - if ((c = strchr(da_login, '/')) != NULL) - { - nexus->login_path = g_strdup(c); - *c = '\0'; - } - - nexus->login_host = g_strdup(da_login); - } - - g_free(nexus->read_buf); - nexus->read_buf = NULL; - nexus->read_len = 0; - - purple_ssl_close(nexus->gsc); - - /* Now begin the connection to the login server. */ - nexus->gsc = purple_ssl_connect(nexus->session->account, - nexus->login_host, PURPLE_SSL_DEFAULT_PORT, - login_connect_cb, login_error_cb, nexus); -} - - -#endif - -/************************************************************************** - * Connect - **************************************************************************/ - -#if 0 /* khc */ -static void -nexus_connect_cb(gpointer data, PurpleSslConnection *gsc, - PurpleInputCondition cond) -{ - MsnNexus *nexus; - MsnSession *session; - - nexus = data; - g_return_if_fail(nexus != NULL); - - session = nexus->session; - g_return_if_fail(session != NULL); - - msn_session_set_login_step(session, MSN_LOGIN_STEP_AUTH); - - nexus->write_buf = g_strdup("GET /rdr/pprdr.asp\r\n\r\n"); - nexus->written_len = 0; - - nexus->read_len = 0; - - nexus->written_cb = nexus_connect_written_cb; - - nexus->input_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, - nexus_write_cb, nexus); - - nexus_write_cb(nexus, gsc->fd, PURPLE_INPUT_WRITE); -} - -#endif - -void -msn_nexus_connect(MsnNexus *nexus) -{ - /* Authenticate via Windows Live ID. */ - msn_soap_init(nexus->soapconn, MSN_TWN_SERVER, 1, nexus_login_connect_cb, nexus_login_error_cb); - msn_soap_connect(nexus->soapconn); -}
--- a/libpurple/protocols/msn/nexus.h Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/msn/nexus.h Sat Nov 17 02:20:01 2007 +0000 @@ -139,7 +139,6 @@ struct _MsnNexus { MsnSession *session; - MsnSoapConn *soapconn; char * challenge_data_str; GHashTable *challenge_data; };
--- a/libpurple/protocols/msn/notification.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/msn/notification.c Sat Nov 17 02:20:01 2007 +0000 @@ -230,11 +230,9 @@ { MsnSession *session; PurpleAccount *account; - PurpleConnection *gc; session = cmdproc->session; account = session->account; - gc = purple_account_get_connection(account); if (!g_ascii_strcasecmp(cmd->params[1], "OK")) { @@ -264,14 +262,15 @@ for (cur = elems; *cur != NULL; cur++) { tokens = g_strsplit(*cur, "=", 2); - if(tokens[0]&&tokens[1]) + if(tokens[0] && tokens[1]) { purple_debug_info("MSNP14","challenge %p,key:%s,value:%s\n", session->nexus->challenge_data,tokens[0],tokens[1]); g_hash_table_insert(session->nexus->challenge_data, tokens[0], tokens[1]); - } - /* Don't free each of the tokens, only the array. */ - g_free(tokens); + /* Don't free each of the tokens, only the array. */ + g_free(tokens); + } else + g_strfreev(tokens); } g_strfreev(elems); @@ -415,7 +414,7 @@ { g_return_if_fail(cmd->payload_cb != NULL); - purple_debug_info("MSNP14","MSG payload:{%s}\n",cmd->payload); + purple_debug_info("MSNP14","MSG payload:{%.*s}\n",cmd->payload_len, cmd->payload); cmd->payload_cb(cmdproc, cmd, cmd->payload, cmd->payload_len); } } @@ -450,7 +449,7 @@ const char *passport; const char *content_type; - purple_debug_info("MSNP14","Process UBM payload:%s\n",payload); + purple_debug_info("MSNP14","Process UBM payload:%.*s\n", len, payload); msg = msn_message_new_from_cmd(cmdproc->session, cmd); msn_message_parse_payload(msg, payload, len,MSG_LINE_DEM,MSG_BODY_DEM); @@ -466,15 +465,12 @@ if(!strcmp(content_type,"text/plain")){ const char *value; const char *body; - char *body_str; char *body_enc; char *body_final = NULL; size_t body_len; body = msn_message_get_bin_data(msg, &body_len); - body_str = g_strndup(body, body_len); - body_enc = g_markup_escape_text(body_str, -1); - g_free(body_str); + body_enc = g_markup_escape_text(body, body_len); if ((value = msn_message_get_attr(msg, "X-MMS-IM-Format")) != NULL) { char *pre, *post; @@ -487,6 +483,7 @@ } g_free(body_enc); serv_got_im(gc, passport, body_final, 0, time(NULL)); + g_free(body_final); } if(!strcmp(content_type,"text/x-msmsgscontrol")){ if(msn_message_get_attr(msg, "TypingUser") != NULL){ @@ -533,7 +530,7 @@ }else{ g_return_if_fail(cmd->payload_cb != NULL); - purple_debug_info("MSNP14","UBM payload:{%s}\n",cmd->payload); + purple_debug_info("MSNP14","UBM payload:{%.*s}\n", cmd->payload_len, cmd->payload); ubm_cmd_post(cmdproc, cmd, cmd->payload, cmd->payload_len); } } @@ -737,7 +734,7 @@ msn_cmdproc_send_trans(cmdproc, trans); g_free(payload); - g_free(tokens); + g_strfreev(tokens); } static void @@ -1203,8 +1200,6 @@ MsnSession *session = cmdproc->session; const char *type, *value, *friendlyname; - purple_debug_info("MSN Notification", "prp_cmd()\n"); - g_return_if_fail(cmd->param_count >= 3); type = cmd->params[2]; @@ -1416,7 +1411,7 @@ { purple_debug_error("msn", "Error opening temp passport file: %s\n", - strerror(errno)); + g_strerror(errno)); } else { @@ -1465,7 +1460,7 @@ { purple_debug_error("msn", "Error closing temp passport file: %s\n", - strerror(errno)); + g_strerror(errno)); g_unlink(session->passport_info.file); g_free(session->passport_info.file); @@ -1618,32 +1613,29 @@ { MsnSession *session; PurpleAccount *account; - PurpleConnection *gc; MsnUser *user; const char *passport; - char *psm_str, *currentmedia_str, *str; - - /*get the payload content*/ -// purple_debug_info("MSNP14","UBX {%s} payload{%s}\n",cmd->params[0], cmd->payload); + char *psm_str, *str; + CurrentMedia media = {NULL, NULL, NULL}; session = cmdproc->session; account = session->account; - gc = purple_account_get_connection(account); passport = cmd->params[0]; user = msn_userlist_find_user(session->userlist, passport); psm_str = msn_get_psm(cmd->payload,len); - currentmedia_str = msn_parse_currentmedia( - str = msn_get_currentmedia(cmd->payload, len)); + msn_user_set_statusline(user, psm_str); + g_free(psm_str); + + str = msn_get_currentmedia(cmd->payload, len); + if (msn_parse_currentmedia(str, &media)) + msn_user_set_currentmedia(user, &media); + else + msn_user_set_currentmedia(user, NULL); g_free(str); - msn_user_set_statusline(user, psm_str); - msn_user_set_currentmedia(user, currentmedia_str); msn_user_update(user); - - g_free(psm_str); - g_free(currentmedia_str); } static void @@ -1797,7 +1789,7 @@ return; /*new a oim session*/ - session->oim = msn_oim_new(session); +// session->oim = msn_oim_new(session); // msn_oim_connect(session->oim); table = msn_message_get_hashtable_from_body(msg);
--- a/libpurple/protocols/msn/object.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/msn/object.c Sat Nov 17 02:20:01 2007 +0000 @@ -154,10 +154,8 @@ { g_return_if_fail(obj != NULL); - if (obj->creator != NULL) - g_free(obj->creator); - - obj->creator = (creator == NULL ? NULL : g_strdup(creator)); + g_free(obj->creator); + obj->creator = g_strdup(creator); } void @@ -181,10 +179,8 @@ { g_return_if_fail(obj != NULL); - if (obj->location != NULL) - g_free(obj->location); - - obj->location = (location == NULL ? NULL : g_strdup(location)); + g_free(obj->location); + obj->location = g_strdup(location); } void @@ -192,10 +188,8 @@ { g_return_if_fail(obj != NULL); - if (obj->friendly != NULL) - g_free(obj->friendly); - - obj->friendly = (friendly == NULL ? NULL : g_strdup(friendly)); + g_free(obj->friendly); + obj->friendly = g_strdup(friendly); } void @@ -203,10 +197,8 @@ { g_return_if_fail(obj != NULL); - if (obj->sha1d != NULL) - g_free(obj->sha1d); - - obj->sha1d = (sha1d == NULL ? NULL : g_strdup(sha1d)); + g_free(obj->sha1d); + obj->sha1d = g_strdup(sha1d); } void @@ -214,10 +206,8 @@ { g_return_if_fail(obj != NULL); - if (obj->sha1c != NULL) - g_free(obj->sha1c); - - obj->sha1c = (sha1c == NULL ? NULL : g_strdup(sha1c)); + g_free(obj->sha1c); + obj->sha1c = g_strdup(sha1c); } const char *
--- a/libpurple/protocols/msn/oim.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/msn/oim.c Sat Nov 17 02:20:01 2007 +0000 @@ -24,24 +24,31 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "msn.h" -#include "soap.h" +#include "soap2.h" #include "oim.h" #include "msnutils.h" +typedef struct _MsnOimSendReq { + char *from_member; + char *friendname; + char *to_member; + char *oim_msg; +} MsnOimSendReq; + +typedef struct { + MsnOim *oim; + char *msg_id; +} MsnOimRecvData; + /*Local Function Prototype*/ -static void msn_oim_post_single_get_msg(MsnOim *oim,const char *msgid); +static void msn_oim_post_single_get_msg(MsnOim *oim, char *msgid); static MsnOimSendReq *msn_oim_new_send_req(const char *from_member, - const char *friendname, - const char* to_member, - gint send_seq, - const char *msg); -static void msn_oim_retrieve_connect_init(MsnSoapConn *soapconn); -static void msn_oim_send_connect_init(MsnSoapConn *soapconn); + const char *friendname, + const char* to_member, + const char *msg); static void msn_oim_free_send_req(MsnOimSendReq *req); -static void msn_oim_report_to_user(MsnOim *oim, const char *msg_str); -static void msn_oim_get_process(MsnOim *oim, const char *oim_msg); +static void msn_oim_report_to_user(MsnOimRecvData *rdata, const char *msg_str); static char *msn_oim_msg_to_str(MsnOim *oim, const char *body); -static void msn_oim_send_process(MsnOim *oim, const char *body, int len); /*new a OIM object*/ MsnOim * @@ -51,10 +58,7 @@ oim = g_new0(MsnOim, 1); oim->session = session; - oim->retrieveconn = msn_soap_new(session,oim,1); - oim->oim_list = NULL; - oim->sendconn = msn_soap_new(session,oim,1); oim->run_id = rand_guid(); oim->challenge = NULL; oim->send_queue = g_queue_new(); @@ -69,8 +73,6 @@ MsnOimSendReq *request; purple_debug_info("OIM","destroy the OIM \n"); - msn_soap_destroy(oim->retrieveconn); - msn_soap_destroy(oim->sendconn); g_free(oim->run_id); g_free(oim->challenge); @@ -84,8 +86,7 @@ static MsnOimSendReq * msn_oim_new_send_req(const char *from_member, const char*friendname, - const char* to_member, gint send_seq, - const char *msg) + const char* to_member, const char *msg) { MsnOimSendReq *request; @@ -93,7 +94,6 @@ request->from_member =g_strdup(from_member); request->friendname = g_strdup(friendname); request->to_member = g_strdup(to_member); - request->send_seq = send_seq; request->oim_msg = g_strdup(msg); return request; } @@ -115,7 +115,7 @@ * OIM send SOAP request * **************************************/ /*encode the message to OIM Message Format*/ -static char * +static gchar * msn_oim_msg_to_str(MsnOim *oim, const char *body) { char *oim_body,*oim_base64; @@ -125,136 +125,80 @@ purple_debug_info("MSN OIM","encoded base64 body:{%s}\n",oim_base64); oim_body = g_strdup_printf(MSN_OIM_MSG_TEMPLATE, oim->run_id,oim->send_seq,oim_base64); + g_free(oim_base64); return oim_body; } -/*oim SOAP server login error*/ -static void -msn_oim_send_error_cb(MsnSoapConn *soapconn, PurpleSslConnection *gsc, PurpleSslErrorType error) -{ - MsnSession *session; - - session = soapconn->session; - g_return_if_fail(session != NULL); - - msn_session_set_error(session, MSN_ERROR_SERV_DOWN, _("Unable to connect to OIM server")); -} - -/*msn oim SOAP server connect process*/ -static gboolean -msn_oim_send_connect_cb(MsnSoapConn *soapconn, PurpleSslConnection *gsc) -{ - MsnSession * session; - MsnOim *oim; - - oim = soapconn->parent; - g_return_val_if_fail(oim != NULL, TRUE); - - session = oim->session; - g_return_val_if_fail(session != NULL, FALSE); - - return TRUE; -} - /* * Process the send return SOAP string * If got SOAP Fault,get the lock key,and resend it. */ static void -msn_oim_send_process(MsnOim *oim, const char *body, int len) +msn_oim_send_read_cb(MsnSoapMessage *request, MsnSoapMessage *response, + gpointer data) { - xmlnode *responseNode, *bodyNode; - xmlnode *faultNode, *faultCodeNode, *faultstringNode; - xmlnode *detailNode, *challengeNode; - char *faultCodeStr = NULL, *faultstring = NULL; + MsnOim *oim = data; + MsnOimSendReq *msg = g_queue_pop_head(oim->send_queue); + + g_return_if_fail(msg != NULL); + + if (response == NULL) { + purple_debug_info("MSNP14", "cannot send OIM: %s\n", msg->oim_msg); + } else { + xmlnode *faultNode = msn_soap_xml_get(response->xml, "Body/Fault"); + + if (faultNode == NULL) { + /*Send OK! return*/ + purple_debug_info("MSNP14", "sent OIM: %s\n", msg->oim_msg); + } else { + xmlnode *faultcode = xmlnode_get_child(faultNode, "faultcode"); + + if (faultcode) { + char *faultcode_str = xmlnode_get_data(faultcode); + + if (g_str_equal(faultcode_str, "q0:AuthenticationFailed")) { + xmlnode *challengeNode = msn_soap_xml_get(faultNode, + "detail/LockKeyChallenge"); + + if (challengeNode == NULL) { + if (oim->challenge) { + g_free(oim->challenge); + oim->challenge = NULL; - responseNode = xmlnode_from_str(body,len); - g_return_if_fail(responseNode != NULL); - bodyNode = xmlnode_get_child(responseNode,"Body"); - faultNode = xmlnode_get_child(bodyNode,"Fault"); - if(faultNode == NULL){ - /*Send OK! return*/ - MsnOimSendReq *request; - - purple_debug_info("MSN OIM","send OIM OK!"); - xmlnode_free(responseNode); - request = g_queue_pop_head(oim->send_queue); - msn_oim_free_send_req(request); - /*send next buffered Offline Message*/ - msn_soap_post(oim->sendconn, NULL); - return; - } - /*get the challenge,and repost it*/ - faultCodeNode = xmlnode_get_child(faultNode,"faultcode"); - if(faultCodeNode == NULL){ - purple_debug_info("MSN OIM","faultcode Node is NULL\n"); - goto oim_send_process_fail; - } - faultCodeStr = xmlnode_get_data(faultCodeNode); - purple_debug_info("MSN OIM","fault code:{%s}\n",faultCodeStr); -#if 0 - if(!strcmp(faultCodeStr,"q0:AuthenticationFailed")){ - /*other Fault Reason?*/ - goto oim_send_process_fail; - } -#endif + purple_debug_info("msnoim","resending OIM: %s\n", + msg->oim_msg); + g_queue_push_head(oim->send_queue, msg); + msn_oim_send_msg(oim); + return; + } else { + purple_debug_info("msnoim", + "can't find lock key for OIM: %s\n", + msg->oim_msg); + } + } else { + char buf[33]; - faultstringNode = xmlnode_get_child(faultNode,"faultstring"); - faultstring = xmlnode_get_data(faultstringNode); - purple_debug_info("MSN OIM","fault string :{%s}\n",faultstring); + char *challenge = xmlnode_get_data(challengeNode); + msn_handle_chl(challenge, buf); + + g_free(oim->challenge); + oim->challenge = g_strndup(buf, sizeof(buf)); + g_free(challenge); + purple_debug_info("MSNP14","lockkey:{%s}\n",oim->challenge); - /* lock key fault reason, - * compute the challenge and resend it - */ - detailNode = xmlnode_get_child(faultNode, "detail"); - if(detailNode == NULL){ - goto oim_send_process_fail; - } - challengeNode = xmlnode_get_child(detailNode,"LockKeyChallenge"); - if (challengeNode == NULL) { - goto oim_send_process_fail; + /*repost the send*/ + purple_debug_info("MSNP14","resending OIM: %s\n", msg->oim_msg); + g_queue_push_head(oim->send_queue, msg); + msn_oim_send_msg(oim); + return; + } + } + } + } } - g_free(oim->challenge); - oim->challenge = xmlnode_get_data(challengeNode); - purple_debug_info("MSN OIM","lockkey:{%s}\n",oim->challenge); - - /*repost the send*/ - purple_debug_info("MSN OIM","prepare to repost the send...\n"); - msn_oim_send_msg(oim); - -oim_send_process_fail: - g_free(faultstring); - g_free(faultCodeStr); - xmlnode_free(responseNode); - return ; -} - -static gboolean -msn_oim_send_read_cb(MsnSoapConn *soapconn) -{ - MsnSession *session = soapconn->session; - MsnOim * oim; - - if (soapconn->body == NULL) - return TRUE; - - g_return_val_if_fail(session != NULL, FALSE); - oim = soapconn->session->oim; - g_return_val_if_fail(oim != NULL, TRUE); - - purple_debug_info("MSN OIM","read buffer:{%s}\n", soapconn->body); - msn_oim_send_process(oim,soapconn->body,soapconn->body_len); - - return TRUE; -} - -static void -msn_oim_send_written_cb(MsnSoapConn *soapconn) -{ - soapconn->read_cb = msn_oim_send_read_cb; -// msn_soap_read_cb(data,source,cond); + msn_oim_free_send_req(msg); } void @@ -262,45 +206,36 @@ const char* friendname, const char *tomember, const char * msg) { - MsnOimSendReq *request; - g_return_if_fail(oim != NULL); - request = msn_oim_new_send_req(membername,friendname,tomember,oim->send_seq,msg); - g_queue_push_tail(oim->send_queue,request); + g_queue_push_tail(oim->send_queue, + msn_oim_new_send_req(membername, friendname, tomember, msg)); } /*post send single message request to oim server*/ void msn_oim_send_msg(MsnOim *oim) { - MsnSoapReq *soap_request; MsnOimSendReq *oim_request; char *soap_body,*mspauth; char *msg_body; - char buf[33]; g_return_if_fail(oim != NULL); - oim_request = g_queue_pop_head(oim->send_queue); + oim_request = g_queue_peek_head(oim->send_queue); g_return_if_fail(oim_request != NULL); - purple_debug_info("MSN OIM","send single OIM Message\n"); + purple_debug_info("MSNP14","sending OIM: %s\n", oim_request->oim_msg); mspauth = g_strdup_printf("t=%s&p=%s", oim->session->passport_info.t, oim->session->passport_info.p ); - g_queue_push_head(oim->send_queue,oim_request); /* if we got the challenge lock key, we compute it * else we go for the SOAP fault and resend it. */ - if(oim->challenge != NULL){ - msn_handle_chl(oim->challenge, buf); - }else{ - purple_debug_info("MSN OIM","no lock key challenge,wait for SOAP Fault and Resend\n"); - buf[0]='\0'; + if(oim->challenge == NULL){ + purple_debug_info("MSNP14","no lock key challenge,wait for SOAP Fault and Resend\n"); } - purple_debug_info("MSN OIM","get the lock key challenge {%s}\n",buf); msg_body = msn_oim_msg_to_str(oim, oim_request->oim_msg); soap_body = g_strdup_printf(MSN_OIM_SEND_TEMPLATE, @@ -309,115 +244,71 @@ oim_request->to_member, mspauth, MSNP13_WLM_PRODUCT_ID, - buf, - oim_request->send_seq, - msg_body - ); - soap_request = msn_soap_request_new(MSN_OIM_SEND_HOST, - MSN_OIM_SEND_URL, - MSN_OIM_SEND_SOAP_ACTION, - soap_body, - NULL, - msn_oim_send_read_cb, - msn_oim_send_written_cb, - msn_oim_send_connect_init); + oim->challenge ? oim->challenge : "", + oim->send_seq, + msg_body); + + msn_soap_message_send(oim->session, + msn_soap_message_new(MSN_OIM_SEND_SOAP_ACTION, + xmlnode_from_str(soap_body, -1)), + MSN_OIM_SEND_HOST, MSN_OIM_SEND_URL, msn_oim_send_read_cb, oim); + + /*increase the offline Sequence control*/ + if (oim->challenge != NULL) { + oim->send_seq++; + } + g_free(mspauth); g_free(msg_body); g_free(soap_body); - - /*increase the offline Sequence control*/ - if(oim->challenge != NULL){ - oim->send_seq++; - } - msn_soap_post(oim->sendconn,soap_request); } /**************************************** * OIM delete SOAP request * **************************************/ -static gboolean -msn_oim_delete_read_cb(MsnSoapConn *soapconn) +static void +msn_oim_delete_read_cb(MsnSoapMessage *request, MsnSoapMessage *response, + gpointer data) { - if (soapconn->body == NULL) - return TRUE; - purple_debug_info("MSN OIM","OIM delete read buffer:{%s}\n",soapconn->body); + MsnOimRecvData *rdata = data; - msn_soap_free_read_buf(soapconn); - /*get next single Offline Message*/ -// msn_soap_post(soapconn,NULL); /* we already do this in soap.c */ - return TRUE; -} + if (response && msn_soap_xml_get(response->xml, "Body/Fault") == NULL) { + purple_debug_info("msnoim", "delete OIM success\n"); + rdata->oim->oim_list = g_list_remove(rdata->oim->oim_list, + rdata->msg_id); + g_free(rdata->msg_id); + } else { + purple_debug_info("msnoim", "delete OIM failed\n"); + } -static void -msn_oim_delete_written_cb(MsnSoapConn *soapconn) -{ - soapconn->read_cb = msn_oim_delete_read_cb; + g_free(rdata); } /*Post to get the Offline Instant Message*/ static void -msn_oim_post_delete_msg(MsnOim *oim,const char *msgid) +msn_oim_post_delete_msg(MsnOimRecvData *rdata) { - MsnSoapReq *soap_request; - const char *soap_body,*t,*p; + MsnOim *oim = rdata->oim; + char *msgid = rdata->msg_id; + char *soap_body; - g_return_if_fail(oim != NULL); - g_return_if_fail(msgid != NULL); - - purple_debug_info("MSN OIM","Delete single OIM Message {%s}\n",msgid); - t = oim->session->passport_info.t; - p = oim->session->passport_info.p; + purple_debug_info("MSNP14","Delete single OIM Message {%s}\n",msgid); soap_body = g_strdup_printf(MSN_OIM_DEL_TEMPLATE, - t, - p, - msgid - ); - soap_request = msn_soap_request_new(MSN_OIM_RETRIEVE_HOST, - MSN_OIM_RETRIEVE_URL, - MSN_OIM_DEL_SOAP_ACTION, - soap_body, - NULL, - msn_oim_delete_read_cb, - msn_oim_delete_written_cb, - msn_oim_retrieve_connect_init); - msn_soap_post(oim->retrieveconn,soap_request); + oim->session->passport_info.t, oim->session->passport_info.p, msgid); + + msn_soap_message_send(oim->session, + msn_soap_message_new(MSN_OIM_DEL_SOAP_ACTION, + xmlnode_from_str(soap_body, -1)), + MSN_OIM_RETRIEVE_HOST, MSN_OIM_RETRIEVE_URL, + msn_oim_delete_read_cb, rdata); + + g_free(soap_body); } /**************************************** * OIM get SOAP request * **************************************/ -/*oim SOAP server login error*/ -static void -msn_oim_get_error_cb(MsnSoapConn *soapconn, PurpleSslConnection *gsc, PurpleSslErrorType error) -{ - MsnSession *session; - - session = soapconn->session; - g_return_if_fail(session != NULL); - msn_soap_clean_unhandled_requests(soapconn); - -// msn_session_set_error(session, MSN_ERROR_SERV_DOWN, _("Unable to connect to OIM server")); -} - -/*msn oim SOAP server connect process*/ -static gboolean -msn_oim_get_connect_cb(MsnSoapConn *soapconn, PurpleSslConnection *gsc) -{ - MsnSession * session; - MsnOim *oim; - - oim = soapconn->parent; - g_return_val_if_fail(oim != NULL, TRUE); - - session = oim->session; - g_return_val_if_fail(session != NULL, FALSE); - - purple_debug_info("MSN OIM","Connected and ready to get OIM!\n"); - - return TRUE; -} - /* like purple_str_to_time, but different. The format of the timestamp * is like this: 5 Sep 2007 21:42:12 -0700 */ static time_t @@ -429,9 +320,13 @@ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL }; + time_t tval = 0; struct tm t; memset(&t, 0, sizeof(t)); + time(&tval); + localtime_r(&tval, &t); + if (sscanf(timestamp, "%02d %03s %04d %02d:%02d:%02d %05s", &t.tm_mday, month_str, &t.tm_year, &t.tm_hour, &t.tm_min, &t.tm_sec, tz_str) == 7) { @@ -460,14 +355,13 @@ tzoff *= -1; t.tm_year -= 1900; - t.tm_isdst = 0; #ifdef _WIN32 if ((sys_tzoff = wpurple_get_tz_offset()) != -1) tzoff += sys_tzoff; #else #ifdef HAVE_TM_GMTOFF - tzoff += t.tm_gmtoff; + tzoff -= t.tm_gmtoff; #else # ifdef HAVE_TIMEZONE tzset(); /* making sure */ @@ -481,13 +375,13 @@ } } - purple_debug_info("MSN OIM:OIM", "Can't parse timestamp %s\n", timestamp); - return time(NULL); + purple_debug_info("MSNP14:OIM", "Can't parse timestamp %s\n", timestamp); + return tval; } /*Post the Offline Instant Message to User Conversation*/ static void -msn_oim_report_to_user(MsnOim *oim, const char *msg_str) +msn_oim_report_to_user(MsnOimRecvData *rdata, const char *msg_str) { MsnMessage *message; char *date,*from,*decode_msg; @@ -496,14 +390,13 @@ char *start,*end; int has_nick = 0; char *passport_str, *passport; - char *msg_id; time_t stamp; message = msn_message_new(MSN_MSG_UNKNOWN); msn_message_parse_payload(message, msg_str, strlen(msg_str), MSG_OIM_LINE_DEM, MSG_OIM_BODY_DEM); - purple_debug_info("MSN OIM","oim body:{%s}\n",message->body); + purple_debug_info("MSNP14","oim body:{%s}\n",message->body); decode_msg = (char *)purple_base64_decode(message->body,&body_len); date = (char *)g_hash_table_lookup(message->attr_table, "Date"); from = (char *)g_hash_table_lookup(message->attr_table, "From"); @@ -513,12 +406,12 @@ if(has_nick){ tokens = g_strsplit(from , " " , 2); passport_str = g_strdup(tokens[1]); - purple_debug_info("MSN OIM","oim Date:{%s},nickname:{%s},tokens[1]:{%s} passport{%s}\n", + purple_debug_info("MSNP14","oim Date:{%s},nickname:{%s},tokens[1]:{%s} passport{%s}\n", date,tokens[0],tokens[1],passport_str); g_strfreev(tokens); }else{ passport_str = g_strdup(from); - purple_debug_info("MSN OIM","oim Date:{%s},passport{%s}\n", + purple_debug_info("MSNP14","oim Date:{%s},passport{%s}\n", date,passport_str); } start = strstr(passport_str,"<"); @@ -530,66 +423,43 @@ stamp = msn_oim_parse_timestamp(date); - serv_got_im(oim->session->account->gc, passport, decode_msg, 0, stamp); + serv_got_im(rdata->oim->session->account->gc, passport, decode_msg, 0, + stamp); /*Now get the oim message ID from the oim_list. * and append to read list to prepare for deleting the Offline Message when sign out */ - if(oim->oim_list != NULL){ - msg_id = oim->oim_list->data; - msn_oim_post_delete_msg(oim,msg_id); - oim->oim_list = g_list_remove(oim->oim_list, oim->oim_list->data); - g_free(msg_id); - } + msn_oim_post_delete_msg(rdata); g_free(passport); + g_free(decode_msg); } /* Parse the XML data, * prepare to report the OIM to user */ static void -msn_oim_get_process(MsnOim *oim, const char *oim_msg) -{ - xmlnode *oim_node,*bodyNode,*responseNode,*msgNode; - char *msg_str; - - oim_node = xmlnode_from_str(oim_msg, strlen(oim_msg)); - bodyNode = xmlnode_get_child(oim_node,"Body"); - responseNode = xmlnode_get_child(bodyNode,"GetMessageResponse"); - msgNode = xmlnode_get_child(responseNode,"GetMessageResult"); - msg_str = xmlnode_get_data(msgNode); - purple_debug_info("OIM","msg:{%s}\n",msg_str); - msn_oim_report_to_user(oim,msg_str); - - g_free(msg_str); - xmlnode_free(oim_node); -} - -static gboolean -msn_oim_get_read_cb(MsnSoapConn *soapconn) +msn_oim_get_read_cb(MsnSoapMessage *request, MsnSoapMessage *response, + gpointer data) { - MsnOim * oim = soapconn->session->oim; - - if (soapconn->body == NULL) - return TRUE; + MsnOimRecvData *rdata = data; - purple_debug_info("MSN OIM","OIM get read buffer:{%s}\n",soapconn->body); - - /*we need to process the read message!*/ - msn_oim_get_process(oim,soapconn->body); - msn_soap_free_read_buf(soapconn); + if (response != NULL) { + xmlnode *msg_node = msn_soap_xml_get(response->xml, + "Body/GetMessageResponse/GetMessageResult"); - /*get next single Offline Message*/ -// msn_soap_post(soapconn,NULL); /* we already do this in soap.c */ - return TRUE; -} - -static void -msn_oim_get_written_cb(MsnSoapConn *soapconn) -{ - soapconn->read_cb = msn_oim_get_read_cb; -// msn_soap_read_cb(data,source,cond); + if (msg_node) { + char *msg_str = xmlnode_get_data(msg_node); + msn_oim_report_to_user(rdata, msg_str); + g_free(msg_str); + } else { + char *str = xmlnode_to_str(response->xml, NULL); + purple_debug_info("msnoim", "Unknown response: %s\n", str); + g_free(str); + } + } else { + purple_debug_info("msnoim", "Failed to get OIM\n"); + } } /* parse the oim XML data @@ -598,115 +468,88 @@ void msn_parse_oim_msg(MsnOim *oim,const char *xmlmsg) { - xmlnode *node, *mNode,*ENode,*INode,*rtNode,*nNode; - char *passport,*msgid,*nickname, *unread, *rTime = NULL; + xmlnode *node, *mNode; + xmlnode *iu_node; MsnSession *session = oim->session; - purple_debug_info("MSN OIM:OIM", "%s", xmlmsg); + purple_debug_info("MSNP14:OIM", "%s", xmlmsg); - node = xmlnode_from_str(xmlmsg, strlen(xmlmsg)); + node = xmlnode_from_str(xmlmsg, -1); if (strcmp(node->name, "MD") != 0) { + purple_debug_info("msnoim", "WTF is this? %s\n", xmlmsg); xmlnode_free(node); return; } - ENode = xmlnode_get_child(node, "E"); - INode = xmlnode_get_child(ENode, "IU"); - unread = xmlnode_get_data(INode); - - if (unread != NULL && purple_account_get_check_mail(session->account)) - { - int count = atoi(unread); + iu_node = msn_soap_xml_get(node, "E/IU"); - if (count > 0) - { - const char *passport; - const char *url; + if (iu_node != NULL && purple_account_get_check_mail(session->account)) + { + char *unread = xmlnode_get_data(iu_node); + const char *passport = msn_user_get_passport(session->user); + const char *url = session->passport_info.file; - passport = msn_user_get_passport(session->user); - url = session->passport_info.file; - - purple_notify_emails(session->account->gc, atoi(unread), FALSE, NULL, NULL, - &passport, &url, NULL, NULL); - } + /* XXX/khc: pretty sure this is wrong */ + purple_notify_emails(session->account->gc, atoi(unread), FALSE, NULL, + NULL, &passport, &url, NULL, NULL); + g_free(unread); } for(mNode = xmlnode_get_child(node, "M"); mNode; mNode = xmlnode_get_next_twin(mNode)){ - /*email Node*/ - ENode = xmlnode_get_child(mNode,"E"); - passport = xmlnode_get_data(ENode); - /*Index */ - INode = xmlnode_get_child(mNode,"I"); - msgid = xmlnode_get_data(INode); - /*Nickname*/ - nNode = xmlnode_get_child(mNode,"N"); - nickname = xmlnode_get_data(nNode); - /*receive time*/ - rtNode = xmlnode_get_child(mNode,"RT"); - if(rtNode != NULL) { - rTime = xmlnode_get_data(rtNode); - rtNode = NULL; + char *passport, *msgid, *nickname, *rtime = NULL; + xmlnode *e_node, *i_node, *n_node, *rt_node; + + e_node = xmlnode_get_child(mNode, "E"); + passport = xmlnode_get_data(e_node); + + i_node = xmlnode_get_child(mNode, "I"); + msgid = xmlnode_get_data(i_node); + + n_node = xmlnode_get_child(mNode, "N"); + nickname = xmlnode_get_data(n_node); + + rt_node = xmlnode_get_child(mNode, "RT"); + if (rt_node != NULL) { + rtime = xmlnode_get_data(rt_node); } -/* purple_debug_info("MSN OIM","E:{%s},I:{%s},rTime:{%s}\n",passport,msgid,rTime); */ +/* purple_debug_info("msnoim","E:{%s},I:{%s},rTime:{%s}\n",passport,msgid,rTime); */ - oim->oim_list = g_list_append(oim->oim_list,strdup(msgid)); - msn_oim_post_single_get_msg(oim,msgid); + if (!g_list_find_custom(oim->oim_list, msgid, (GCompareFunc)strcmp)) { + oim->oim_list = g_list_append(oim->oim_list, msgid); + msn_oim_post_single_get_msg(oim, msgid); + msgid = NULL; + } + g_free(passport); g_free(msgid); - g_free(rTime); - rTime = NULL; + g_free(rtime); g_free(nickname); } - g_free(unread); + xmlnode_free(node); } /*Post to get the Offline Instant Message*/ static void -msn_oim_post_single_get_msg(MsnOim *oim,const char *msgid) +msn_oim_post_single_get_msg(MsnOim *oim, char *msgid) { - MsnSoapReq *soap_request; - const char *soap_body,*t,*p; + char *soap_body; + MsnOimRecvData *data = g_new0(MsnOimRecvData, 1); - purple_debug_info("MSN OIM","Get single OIM Message\n"); - t = oim->session->passport_info.t; - p = oim->session->passport_info.p; + purple_debug_info("MSNP14","Get single OIM Message\n"); + + data->oim = oim; + data->msg_id = msgid; soap_body = g_strdup_printf(MSN_OIM_GET_TEMPLATE, - t, - p, - msgid - ); - soap_request = msn_soap_request_new(MSN_OIM_RETRIEVE_HOST, - MSN_OIM_RETRIEVE_URL, - MSN_OIM_GET_SOAP_ACTION, - soap_body, - NULL, - msn_oim_get_read_cb, - msn_oim_get_written_cb, - msn_oim_retrieve_connect_init); - msn_soap_post(oim->retrieveconn,soap_request); -} + oim->session->passport_info.t, oim->session->passport_info.p, msgid); -/*msn oim retrieve server connect init */ -static void -msn_oim_retrieve_connect_init(MsnSoapConn *soapconn) -{ - purple_debug_info("MSN OIM","Initializing OIM retrieve connection\n"); - msn_soap_init(soapconn, MSN_OIM_RETRIEVE_HOST, 1, - msn_oim_get_connect_cb, - msn_oim_get_error_cb); -} + msn_soap_message_send(oim->session, + msn_soap_message_new(MSN_OIM_GET_SOAP_ACTION, + xmlnode_from_str(soap_body, -1)), + MSN_OIM_RETRIEVE_HOST, MSN_OIM_RETRIEVE_URL, + msn_oim_get_read_cb, data); -/*Msn OIM Send Server Connect Init Function*/ -static void -msn_oim_send_connect_init(MsnSoapConn *sendconn) -{ - purple_debug_info("MSN OIM","Initializing OIM send connection\n"); - msn_soap_init(sendconn, MSN_OIM_SEND_HOST, 1, - msn_oim_send_connect_cb, - msn_oim_send_error_cb); + g_free(soap_body); } - -/* EOF oim.c*/
--- a/libpurple/protocols/msn/oim.h Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/msn/oim.h Sat Nov 17 02:20:01 2007 +0000 @@ -95,17 +95,6 @@ "</soap:Body>"\ "</soap:Envelope>" -typedef struct _MsnOimSendReq MsnOimSendReq; - -struct _MsnOimSendReq -{ - char *from_member; - char *friendname; - char *to_member; - char *oim_msg; - gint send_seq; -}; - typedef struct _MsnOim MsnOim; struct _MsnOim @@ -127,7 +116,6 @@ * **************************************************/ MsnOim * msn_oim_new(MsnSession *session); void msn_oim_destroy(MsnOim *oim); -void msn_oim_connect(MsnOim *oim); void msn_parse_oim_msg(MsnOim *oim,const char *xmlmsg); @@ -138,11 +126,5 @@ void msn_oim_send_msg(MsnOim *oim); -/*get the OIM message*/ -void msn_oim_get_msg(MsnOim *oim); - -/*report the oim message to the conversation*/ -void msn_oim_report_user(MsnOim *oim,const char *passport,char *msg); - #endif/* _MSN_OIM_H_*/ /*endof oim.h*/
--- a/libpurple/protocols/msn/page.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/msn/page.c Sat Nov 17 02:20:01 2007 +0000 @@ -74,9 +74,7 @@ g_return_if_fail(page != NULL); g_return_if_fail(body != NULL); - if (page->body != NULL) - g_free(page->body); - + g_free(page->body); page->body = g_strdup(body); }
--- a/libpurple/protocols/msn/servconn.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/msn/servconn.c Sat Nov 17 02:20:01 2007 +0000 @@ -402,7 +402,7 @@ default: purple_debug_error("msn", "servconn read error," "len: %d, errno: %d, error: %s\n", - len, errno, strerror(errno)); + len, errno, g_strerror(errno)); msn_servconn_got_error(servconn, MSN_SERVCONN_ERROR_READ); return; @@ -480,6 +480,7 @@ create_listener(int port) { int fd; + int flags; const int on = 1; #if 0 @@ -555,7 +556,8 @@ return -1; } - fcntl(fd, F_SETFL, O_NONBLOCK); + flags = fcntl(fd, F_GETFL); + fcntl(fd, F_SETFL, flags | O_NONBLOCK); return fd; }
--- a/libpurple/protocols/msn/session.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/msn/session.c Sat Nov 17 02:20:01 2007 +0000 @@ -24,6 +24,7 @@ #include "msn.h" #include "session.h" #include "notification.h" +#include "oim.h" #include "dialog.h" @@ -42,6 +43,7 @@ session->user = msn_user_new(session->userlist, purple_account_get_username(account), NULL); + session->oim = msn_oim_new(session); /*if you want to chat with Yahoo Messenger*/ //session->protocol_ver = WLM_YAHOO_PROT_VER; @@ -99,6 +101,12 @@ if (session->user != NULL) msn_user_destroy(session->user); + if (session->soap_table) + g_hash_table_destroy(session->soap_table); + + if (session->soap_cleanup_handle) + purple_timeout_remove(session->soap_cleanup_handle); + g_free(session); } @@ -130,7 +138,9 @@ msn_session_disconnect(MsnSession *session) { g_return_if_fail(session != NULL); - g_return_if_fail(session->connected); + + if (!session->connected) + return; session->connected = FALSE; @@ -323,6 +333,7 @@ const char *info) { PurpleConnection *gc; + PurpleConnectionError reason; char *msg; gc = purple_account_get_connection(session->account); @@ -330,49 +341,56 @@ switch (error) { case MSN_ERROR_SERVCONN: + reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; msg = g_strdup(info); break; case MSN_ERROR_UNSUPPORTED_PROTOCOL: + reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; msg = g_strdup(_("Our protocol is not supported by the " "server.")); break; case MSN_ERROR_HTTP_MALFORMED: + reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; msg = g_strdup(_("Error parsing HTTP.")); break; case MSN_ERROR_SIGN_OTHER: - gc->wants_to_die = TRUE; + reason = PURPLE_CONNECTION_ERROR_NAME_IN_USE; msg = g_strdup(_("You have signed on from another location.")); if (!purple_account_get_remember_password(session->account)) purple_account_set_password(session->account, NULL); break; case MSN_ERROR_SERV_UNAVAILABLE: + reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; msg = g_strdup(_("The MSN servers are temporarily " "unavailable. Please wait and try " "again.")); break; case MSN_ERROR_SERV_DOWN: + reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; msg = g_strdup(_("The MSN servers are going down " "temporarily.")); break; case MSN_ERROR_AUTH: - gc->wants_to_die = TRUE; + reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED; msg = g_strdup_printf(_("Unable to authenticate: %s"), (info == NULL ) ? _("Unknown error") : info); break; case MSN_ERROR_BAD_BLIST: + reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; msg = g_strdup(_("Your MSN buddy list is temporarily " "unavailable. Please wait and try " "again.")); break; default: + reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; msg = g_strdup(_("Unknown error.")); break; } msn_session_disconnect(session); - purple_connection_error(gc, msg); + purple_connection_error_reason (gc, reason, msg); g_free(msg); }
--- a/libpurple/protocols/msn/session.h Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/msn/session.h Sat Nov 17 02:20:01 2007 +0000 @@ -126,6 +126,9 @@ char *client_ip; int client_port; } passport_info; + + GHashTable *soap_table; + int soap_cleanup_handle; }; /**
--- a/libpurple/protocols/msn/slp.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/msn/slp.c Sat Nov 17 02:20:01 2007 +0000 @@ -505,6 +505,9 @@ int port; nonce = get_token(content, "Nonce: {", "}\r\n"); + if (ip_addrs == NULL) + return; + ip_addrs = get_token(content, "IPv4Internal-Addrs: ", "\r\n"); temp = get_token(content, "IPv4Internal-Port: ", "\r\n"); @@ -514,9 +517,6 @@ port = -1; g_free(temp); - if (ip_addrs == NULL) - return; - if (port > 0) got_transresp(slpcall, nonce, ip_addrs, port); @@ -598,6 +598,9 @@ int port; nonce = get_token(content, "Nonce: {", "}\r\n"); + if (ip_addrs == NULL) + return; + ip_addrs = get_token(content, "IPv4Internal-Addrs: ", "\r\n"); temp = get_token(content, "IPv4Internal-Port: ", "\r\n"); @@ -607,9 +610,6 @@ port = -1; g_free(temp); - if (ip_addrs == NULL) - return; - if (port > 0) got_transresp(slpcall, nonce, ip_addrs, port);
--- a/libpurple/protocols/msn/slplink.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/msn/slplink.c Sat Nov 17 02:20:01 2007 +0000 @@ -39,22 +39,17 @@ char *tmp; char *dir; char *pload; - FILE *tf; int c; gsize pload_size; dir = send ? "send" : "recv"; c = send ? m_sc++ : m_rc++; tmp = g_strdup_printf("%s/msntest/%s/%03d", g_get_home_dir(), dir, c); - tf = g_fopen(tmp, "wb"); - if (tf == NULL) + pload = msn_message_gen_payload(msg, &pload_size); + if (!purple_util_write_data_to_file_absolute(tmp, pload, pload_size)) { - purple_debug_error("msn", "could not open debug file\n"); - return; + purple_debug_error("msn", "could not save debug file"); } - pload = msn_message_gen_payload(msg, &pload_size); - fwrite(pload, 1, pload_size, tf); - fclose(tf); g_free(tmp); } #endif @@ -120,6 +115,8 @@ while (slplink->slp_calls != NULL) msn_slp_call_destroy(slplink->slp_calls->data); + g_queue_free(slplink->slp_msg_queue); + session->slplinks = g_list_remove(session->slplinks, slplink);
--- a/libpurple/protocols/msn/soap.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/msn/soap.c Sat Nov 17 02:20:01 2007 +0000 @@ -26,7 +26,7 @@ #include "msn.h" #include "soap.h" - +#define MSN_SOAP_DEBUG /*local function prototype*/ void msn_soap_set_process_step(MsnSoapConn *soapconn, MsnSoapStep step); @@ -49,17 +49,16 @@ soapconn->step = step; } -//msn_soap_new(MsnSession *session,gpointer data,int sslconn) /*new a soap connection*/ MsnSoapConn * -msn_soap_new(MsnSession *session,gpointer data,int sslconn) +msn_soap_new(MsnSession *session,gpointer data, gboolean ssl) { MsnSoapConn *soapconn; soapconn = g_new0(MsnSoapConn, 1); soapconn->session = session; soapconn->parent = data; - soapconn->ssl_conn = sslconn; + soapconn->ssl_conn = ssl; soapconn->gsc = NULL; soapconn->input_handler = 0; @@ -127,11 +126,12 @@ /*init the soap connection*/ void -msn_soap_init(MsnSoapConn *soapconn,char * host,int ssl, +msn_soap_init(MsnSoapConn *soapconn,char * host, gboolean ssl, MsnSoapSslConnectCbFunction connect_cb, MsnSoapSslErrorCbFunction error_cb) { purple_debug_misc("MSN SOAP","Initializing SOAP connection\n"); + g_free(soapconn->login_host); soapconn->login_host = g_strdup(host); soapconn->ssl_conn = ssl; soapconn->connect_cb = connect_cb; @@ -205,11 +205,9 @@ void msn_soap_destroy(MsnSoapConn *soapconn) { - if(soapconn->login_host) - g_free(soapconn->login_host); + g_free(soapconn->login_host); - if(soapconn->login_path) - g_free(soapconn->login_path); + g_free(soapconn->login_path); /*remove the write handler*/ if (soapconn->output_handler > 0){ @@ -276,7 +274,7 @@ default : purple_debug_error("MSN SOAP", "Read error!" "read len: %d, error = %s\n", - len, strerror(errno)); + len, g_strerror(errno)); purple_input_remove(soapconn->input_handler); //soapconn->input_handler = 0; g_free(soapconn->read_buf); @@ -310,7 +308,7 @@ } /*read the whole SOAP server response*/ -void +static void msn_soap_read_cb(gpointer data, gint source, PurpleInputCondition cond) { MsnSoapConn *soapconn = data; @@ -681,6 +679,8 @@ msn_soap_set_process_step(soapconn, MSN_SOAP_PROCESSING); + /* Ideally this wouldn't ever be necessary, but i believe that it is leaking the previous value */ + g_free(soapconn->write_buf); soapconn->write_buf = write_buf; soapconn->written_len = 0; soapconn->written_cb = written_cb; @@ -765,7 +765,7 @@ * if not connected, Connected first. */ void -msn_soap_post(MsnSoapConn *soapconn,MsnSoapReq *request) +msn_soap_post(MsnSoapConn *soapconn, MsnSoapReq *request) { MsnSoapReq *head_request; @@ -809,17 +809,21 @@ return; } +#ifdef MSN_SOAP_DEBUG purple_debug_info("MSN SOAP", "Currently processing another SOAP request\n"); } else { purple_debug_info("MSN SOAP", "No requests left to dispatch\n"); } +#else + } +#endif + } /*Post the soap request action*/ void msn_soap_post_request(MsnSoapConn *soapconn, MsnSoapReq *request) { - char * soap_head = NULL; char * request_str = NULL; #ifdef MSN_SOAP_DEBUG #if !defined(_WIN32) @@ -829,7 +833,7 @@ #endif msn_soap_set_process_step(soapconn, MSN_SOAP_PROCESSING); - soap_head = g_strdup_printf( + request_str = g_strdup_printf( "POST %s HTTP/1.1\r\n" "SOAPAction: %s\r\n" "Content-Type:text/xml; charset=utf-8\r\n" @@ -839,20 +843,21 @@ "Host: %s\r\n" "Content-Length: %" G_GSIZE_FORMAT "\r\n" "Connection: Keep-Alive\r\n" - "Cache-Control: no-cache\r\n\r\n", + "Cache-Control: no-cache\r\n\r\n" + "%s", request->login_path, request->soap_action, soapconn->session->passport_info.mspauth, request->login_host, - strlen(request->body) + strlen(request->body), + request->body ); - request_str = g_strdup_printf("%s%s", soap_head, request->body); #if defined(MSN_SOAP_DEBUG) && !defined(_WIN32) node = xmlnode_from_str(request->body, -1); if (node != NULL) { char *formattedstr = xmlnode_to_formatted_str(node, NULL); - purple_debug_info("MSN SOAP","Posting request to SOAP server:\n%s%s\n",soap_head, formattedstr); + purple_debug_info("MSN SOAP","Posting request to SOAP server:\n%s%s\n",request_str, formattedstr); g_free(formattedstr); xmlnode_free(node); } @@ -860,7 +865,6 @@ purple_debug_info("MSN SOAP","Failed to parse SOAP request being sent:\n%s\n", request_str); #endif - g_free(soap_head); /*free read buffer*/ // msn_soap_free_read_buf(soapconn); /*post it to server*/
--- a/libpurple/protocols/msn/soap.h Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/msn/soap.h Sat Nov 17 02:20:01 2007 +0000 @@ -83,9 +83,9 @@ MsnSoapStep step; /*ssl connection?*/ - guint ssl_conn; + gboolean ssl_conn; /*normal connection*/ - guint fd; + guint fd; /*SSL connection*/ PurpleSslConnection *gsc; /*ssl connection callback*/ @@ -135,13 +135,13 @@ void msn_soap_post_head_request(MsnSoapConn *soapconn); /*new a soap conneciton */ -MsnSoapConn *msn_soap_new(MsnSession *session,gpointer data,int sslconn); +MsnSoapConn *msn_soap_new(MsnSession *session, gpointer data, gboolean ssl); /*destroy */ void msn_soap_destroy(MsnSoapConn *soapconn); /*init a soap conneciton */ -void msn_soap_init(MsnSoapConn *soapconn, char * host, int ssl, +void msn_soap_init(MsnSoapConn *soapconn, char * host, gboolean ssl, MsnSoapSslConnectCbFunction connect_cb, MsnSoapSslErrorCbFunction error_cb); void msn_soap_connect(MsnSoapConn *soapconn); @@ -154,7 +154,6 @@ void msn_soap_free_read_buf(MsnSoapConn *soapconn); void msn_soap_free_write_buf(MsnSoapConn *soapconn); void msn_soap_connect_cb(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond); -void msn_soap_read_cb(gpointer data, gint source, PurpleInputCondition cond); /*clean the unhandled requests*/ void msn_soap_clean_unhandled_requests(MsnSoapConn *soapconn);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msn/soap2.c Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,677 @@ +/** + * @file soap2.c + * C file for SOAP connection related process + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "internal.h" + +#include "soap2.h" + +#include "session.h" + +#include "debug.h" +#include "xmlnode.h" + +#include <glib.h> +#include <error.h> + +#define SOAP_TIMEOUT (5 * 60) + +typedef struct _MsnSoapRequest { + char *path; + MsnSoapMessage *message; + MsnSoapCallback cb; + gpointer cb_data; +} MsnSoapRequest; + +typedef struct _MsnSoapConnection { + MsnSession *session; + char *host; + + time_t last_used; + PurpleSslConnection *ssl; + gboolean connected; + + guint event_handle; + GString *buf; + gsize handled_len; + gsize body_len; + int response_code; + gboolean headers_done; + gboolean close_when_done; + + MsnSoapMessage *message; + + GQueue *queue; + MsnSoapRequest *current_request; +} MsnSoapConnection; + +static void msn_soap_connection_destroy_foreach_cb(gpointer item, gpointer data); +static gboolean msn_soap_connection_run(gpointer data); + +static MsnSoapConnection *msn_soap_connection_new(MsnSession *session, + const char *host); +static void msn_soap_connection_handle_next(MsnSoapConnection *conn); +static void msn_soap_connection_destroy(MsnSoapConnection *conn); + +static void msn_soap_message_send_internal(MsnSession *session, + MsnSoapMessage *message, const char *host, const char *path, + MsnSoapCallback cb, gpointer cb_data, gboolean first); + +static void msn_soap_request_destroy(MsnSoapRequest *req); +static void msn_soap_connection_sanitize(MsnSoapConnection *conn, gboolean disconnect); + +static gboolean +msn_soap_cleanup_each(gpointer key, gpointer value, gpointer data) +{ + MsnSoapConnection *conn = value; + time_t *t = data; + + if ((*t - conn->last_used) > SOAP_TIMEOUT * 2) { + purple_debug_info("soap", "cleaning up soap conn %p\n", conn); + return TRUE; + } + + return FALSE; +} + +static gboolean +msn_soap_cleanup_for_session(gpointer data) +{ + MsnSession *sess = data; + time_t t = time(NULL); + + purple_debug_info("soap", "session cleanup timeout\n"); + + if (sess->soap_table) { + g_hash_table_foreach_remove(sess->soap_table, msn_soap_cleanup_each, + &t); + + if (g_hash_table_size(sess->soap_table) == 0) { + purple_timeout_remove(sess->soap_cleanup_handle); + sess->soap_cleanup_handle = 0; + } + } + + return TRUE; +} + +static MsnSoapConnection * +msn_soap_get_connection(MsnSession *session, const char *host) +{ + MsnSoapConnection *conn = NULL; + + if (session->soap_table) { + conn = g_hash_table_lookup(session->soap_table, host); + } else { + session->soap_table = g_hash_table_new_full(g_str_hash, g_str_equal, + NULL, (GDestroyNotify)msn_soap_connection_destroy); + } + + if (session->soap_cleanup_handle == 0) + session->soap_cleanup_handle = purple_timeout_add(SOAP_TIMEOUT * 1000, + msn_soap_cleanup_for_session, session); + + if (conn == NULL) { + conn = msn_soap_connection_new(session, host); + g_hash_table_insert(session->soap_table, conn->host, conn); + } + + conn->last_used = time(NULL); + + return conn; +} + +static MsnSoapConnection * +msn_soap_connection_new(MsnSession *session, const char *host) +{ + MsnSoapConnection *conn = g_new0(MsnSoapConnection, 1); + conn->session = session; + conn->host = g_strdup(host); + conn->queue = g_queue_new(); + return conn; +} + +static void +msn_soap_connected_cb(gpointer data, PurpleSslConnection *ssl, + PurpleInputCondition cond) +{ + MsnSoapConnection *conn = data; + + conn->connected = TRUE; + + if (conn->event_handle == 0) + conn->event_handle = purple_timeout_add(0, msn_soap_connection_run, conn); +} + +static void +msn_soap_error_cb(PurpleSslConnection *ssl, PurpleSslErrorType error, + gpointer data) +{ + MsnSoapConnection *conn = data; + + g_hash_table_remove(conn->session->soap_table, conn->host); +} + +static gboolean +msn_soap_handle_redirect(MsnSoapConnection *conn, const char *url) +{ + char *c; + + /* Skip the http:// */ + if ((c = strchr(url, '/')) != NULL) + url += 2; + + if ((c = strchr(url, '/')) != NULL) { + char *host, *path; + + host = g_strndup(url, c - url); + path = g_strdup(c); + + msn_soap_message_send_internal(conn->session, + conn->current_request->message, host, path, + conn->current_request->cb, conn->current_request->cb_data, TRUE); + + msn_soap_request_destroy(conn->current_request); + conn->current_request = NULL; + + g_free(host); + g_free(path); + + return TRUE; + } + + return FALSE; +} + +static gboolean +msn_soap_handle_body(MsnSoapConnection *conn, MsnSoapMessage *response) +{ + xmlnode *body = xmlnode_get_child(response->xml, "Body"); + + if (body) { + MsnSoapRequest *request; + + if (strcmp(body->name, "Fault") == 0) { + xmlnode *fault = xmlnode_get_child(body, "faultcode"); + + if (fault != NULL) { + char *faultdata = xmlnode_get_data(fault); + + if (strcmp(faultdata, "psf:Redirect") == 0) { + xmlnode *url = xmlnode_get_child(body, "redirectUrl"); + + if (url) { + char *urldata = xmlnode_get_data(url); + msn_soap_handle_redirect(conn, urldata); + g_free(urldata); + } + + g_free(faultdata); + return TRUE; + } else if (strcmp(faultdata, "wsse:FailedAuthentication") == 0) { + xmlnode *reason = xmlnode_get_child(body, "faultstring"); + char *reasondata = xmlnode_get_data(reason); + + msn_soap_connection_sanitize(conn, TRUE); + msn_session_set_error(conn->session, MSN_ERROR_AUTH, + reasondata); + + g_free(reasondata); + g_free(faultdata); + return FALSE; + } + + g_free(faultdata); + } + } + + request = conn->current_request; + conn->current_request = NULL; + request->cb(request->message, response, + request->cb_data); + msn_soap_request_destroy(request); + } + + return TRUE; +} + +static void +msn_soap_read_cb(gpointer data, gint fd, PurpleInputCondition cond) +{ + MsnSoapConnection *conn = data; + int count = 0, cnt; + char buf[8192]; + char *linebreak; + char *cursor; + gboolean handled = FALSE; + + if (conn->message == NULL) { + conn->message = msn_soap_message_new(NULL, NULL); + } + + while ((cnt = purple_ssl_read(conn->ssl, buf, sizeof(buf))) > 0) { + purple_debug_info("soap", "read %d bytes\n", cnt); + count += cnt; + if (conn->buf == NULL) { + conn->buf = g_string_new_len(buf, cnt); + } else { + g_string_append_len(conn->buf, buf, cnt); + } + } + + if (cnt < 0) { + if (errno != EAGAIN) { + purple_debug_info("soap", "read: %s\n", g_strerror(errno)); + purple_ssl_close(conn->ssl); + conn->ssl = NULL; + msn_soap_connection_handle_next(conn); + return; + } else if (count == 0) { + return; + } + } + + if (cnt == 0 && count == 0) { + purple_debug_info("soap", "msn_soap_read_cb() called, but no data available?\n"); + purple_ssl_close(conn->ssl); + conn->ssl = NULL; + msn_soap_connection_handle_next(conn); + return; + } + + purple_debug_info("soap", "current %s\n", conn->buf->str); + + cursor = conn->buf->str + conn->handled_len; + + if (!conn->headers_done) { + while ((linebreak = strstr(cursor, "\r\n")) != NULL) { + conn->handled_len = linebreak - conn->buf->str + 2; + + if (conn->response_code == 0) { + if (sscanf(cursor, "HTTP/1.1 %d", &conn->response_code) != 1) { + /* something horribly wrong */ + purple_ssl_close(conn->ssl); + conn->ssl = NULL; + msn_soap_connection_handle_next(conn); + handled = TRUE; + break; + } else if (conn->response_code == 503) { + msn_soap_connection_sanitize(conn, TRUE); + msn_session_set_error(conn->session, MSN_ERROR_SERV_UNAVAILABLE, NULL); + return; + } + } else if (cursor == linebreak) { + /* blank line */ + conn->headers_done = TRUE; + cursor = conn->buf->str + conn->handled_len; + break; + } else { + char *line = g_strndup(cursor, linebreak - cursor); + char *sep = strstr(line, ": "); + char *key = line; + char *value; + + if (sep == NULL) { + purple_debug_info("soap", "ignoring malformed line: %s\n", line); + g_free(line); + goto loop_end; + } + + value = sep + 2; + *sep = '\0'; + msn_soap_message_add_header(conn->message, key, value); + + if ((conn->response_code == 301 || conn->response_code == 300) + && strcmp(key, "Location") == 0) { + + msn_soap_handle_redirect(conn, value); + + handled = TRUE; + g_free(line); + break; + } else if (conn->response_code == 401 && + strcmp(key, "WWW-Authenticate") == 0) { + char *error = strstr(value, "cbtxt="); + + if (error) { + error += strlen("cbtxt="); + } + + msn_soap_connection_sanitize(conn, TRUE); + msn_session_set_error(conn->session, MSN_ERROR_AUTH, + error ? purple_url_decode(error) : NULL); + + g_free(line); + return; + } else if (strcmp(key, "Content-Length") == 0) { + conn->body_len = atoi(value); + } else if (strcmp(key, "Connection") == 0) { + if (strcmp(value, "close") == 0) { + conn->close_when_done = TRUE; + } + } + g_free(line); + } + + loop_end: + cursor = conn->buf->str + conn->handled_len; + } + } + + if (!handled && conn->headers_done) { + if (conn->buf->len - conn->handled_len >= + conn->body_len) { + xmlnode *node = xmlnode_from_str(cursor, conn->body_len); + + if (node == NULL) { + purple_debug_info("soap", "Malformed SOAP response: %s\n", + cursor); + } else { + MsnSoapMessage *message = conn->message; + conn->message = NULL; + message->xml = node; + + if (!msn_soap_handle_body(conn, message)) + return; + } + + msn_soap_connection_handle_next(conn); + } + + return; + } + + if (handled) { + msn_soap_connection_handle_next(conn); + } +} + +static void +msn_soap_write_cb(gpointer data, gint fd, PurpleInputCondition cond) +{ + MsnSoapConnection *conn = data; + int written; + + g_return_if_fail(cond == PURPLE_INPUT_WRITE); + + written = purple_ssl_write(conn->ssl, conn->buf->str + conn->handled_len, + conn->buf->len - conn->handled_len); + + if (written < 0 && errno == EAGAIN) + return; + else if (written <= 0) { + purple_ssl_close(conn->ssl); + conn->ssl = NULL; + msn_soap_connection_handle_next(conn); + return; + } + + conn->handled_len += written; + + if (conn->handled_len < conn->buf->len) + return; + + /* we are done! */ + g_string_free(conn->buf, TRUE); + conn->buf = NULL; + conn->handled_len = 0; + conn->body_len = 0; + conn->response_code = 0; + conn->headers_done = FALSE; + conn->close_when_done = FALSE; + + purple_input_remove(conn->event_handle); + conn->event_handle = purple_input_add(conn->ssl->fd, PURPLE_INPUT_READ, + msn_soap_read_cb, conn); +} + +static gboolean +msn_soap_connection_run(gpointer data) +{ + MsnSoapConnection *conn = data; + MsnSoapRequest *req = g_queue_peek_head(conn->queue); + + conn->event_handle = 0; + + if (req) { + if (conn->ssl == NULL) { + conn->ssl = purple_ssl_connect(conn->session->account, conn->host, + 443, msn_soap_connected_cb, msn_soap_error_cb, conn); + } else if (conn->connected) { + int len = -1; + char *body = xmlnode_to_str(req->message->xml, &len); + GSList *iter; + char *authstr = NULL; + + g_queue_pop_head(conn->queue); + + conn->buf = g_string_new(""); + + if (conn->session->passport_info.mspauth) + authstr = g_strdup_printf("Cookie: MSPAuth=%s\r\n", + conn->session->passport_info.mspauth); + + + g_string_append_printf(conn->buf, + "POST %s HTTP/1.1\r\n" + "SOAPAction: %s\r\n" + "Content-Type:text/xml; charset=utf-8\r\n" + "%s" + "User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)\r\n" + "Accept: */*\r\n" + "Host: %s\r\n" + "Content-Length: %d\r\n" + "Connection: Keep-Alive\r\n" + "Cache-Control: no-cache\r\n", + req->path, req->message->action ? req->message->action : "", + authstr ? authstr : "", conn->host, len); + + for (iter = req->message->headers; iter; iter = iter->next) { + g_string_append(conn->buf, (char *)iter->data); + g_string_append(conn->buf, "\r\n"); + } + + g_string_append(conn->buf, "\r\n"); + g_string_append(conn->buf, body); + + purple_debug_info("soap", "%s\n", conn->buf->str); + + conn->handled_len = 0; + conn->current_request = req; + + conn->event_handle = purple_input_add(conn->ssl->fd, + PURPLE_INPUT_WRITE, msn_soap_write_cb, conn); + msn_soap_write_cb(conn, conn->ssl->fd, PURPLE_INPUT_WRITE); + + g_free(authstr); + g_free(body); + } + } + + return FALSE; +} + +void +msn_soap_message_send(MsnSession *session, MsnSoapMessage *message, + const char *host, const char *path, + MsnSoapCallback cb, gpointer cb_data) +{ + msn_soap_message_send_internal(session, message, host, path, cb, cb_data, + FALSE); +} + +static void +msn_soap_message_send_internal(MsnSession *session, + MsnSoapMessage *message, const char *host, const char *path, + MsnSoapCallback cb, gpointer cb_data, gboolean first) +{ + MsnSoapConnection *conn = msn_soap_get_connection(session, host); + MsnSoapRequest *req = g_new0(MsnSoapRequest, 1); + + req->path = g_strdup(path); + req->message = message; + req->cb = cb; + req->cb_data = cb_data; + + if (first) { + g_queue_push_head(conn->queue, req); + } else { + g_queue_push_tail(conn->queue, req); + } + + if (conn->event_handle == 0) + conn->event_handle = purple_timeout_add(0, msn_soap_connection_run, + conn); +} + +static void +msn_soap_connection_sanitize(MsnSoapConnection *conn, gboolean disconnect) +{ + if (conn->event_handle) { + purple_input_remove(conn->event_handle); + conn->event_handle = 0; + } + + if (conn->message) { + msn_soap_message_destroy(conn->message); + conn->message = NULL; + } + + if (conn->buf) { + g_string_free(conn->buf, TRUE); + conn->buf = NULL; + } + + if (conn->ssl && (disconnect || conn->close_when_done)) { + purple_ssl_close(conn->ssl); + conn->ssl = NULL; + } + + if (conn->current_request) { + msn_soap_request_destroy(conn->current_request); + conn->current_request = NULL; + } +} + +static void +msn_soap_connection_handle_next(MsnSoapConnection *conn) +{ + msn_soap_connection_sanitize(conn, FALSE); + + conn->event_handle = purple_timeout_add(0, msn_soap_connection_run, conn); + + if (conn->current_request) { + MsnSoapRequest *req = conn->current_request; + conn->current_request = NULL; + msn_soap_connection_destroy_foreach_cb(req, conn); + } +} + +static void +msn_soap_connection_destroy_foreach_cb(gpointer item, gpointer data) +{ + MsnSoapRequest *req = item; + + if (req->cb) + req->cb(req->message, NULL, req->cb_data); + + msn_soap_request_destroy(req); +} + +static void +msn_soap_connection_destroy(MsnSoapConnection *conn) +{ + if (conn->current_request) { + MsnSoapRequest *req = conn->current_request; + conn->current_request = NULL; + msn_soap_connection_destroy_foreach_cb(req, conn); + } + + msn_soap_connection_sanitize(conn, TRUE); + g_queue_foreach(conn->queue, msn_soap_connection_destroy_foreach_cb, conn); + g_queue_free(conn->queue); + + g_free(conn->host); + g_free(conn); +} + +MsnSoapMessage * +msn_soap_message_new(const char *action, xmlnode *xml) +{ + MsnSoapMessage *message = g_new0(MsnSoapMessage, 1); + + message->action = g_strdup(action); + message->xml = xml; + + return message; +} + +void +msn_soap_message_destroy(MsnSoapMessage *message) +{ + if (message) { + g_slist_foreach(message->headers, (GFunc)g_free, NULL); + g_slist_free(message->headers); + g_free(message->action); + if (message->xml) + xmlnode_free(message->xml); + g_free(message); + } +} + +void +msn_soap_message_add_header(MsnSoapMessage *req, + const char *name, const char *value) +{ + char *header = g_strdup_printf("%s: %s\r\n", name, value); + + req->headers = g_slist_prepend(req->headers, header); +} + +static void +msn_soap_request_destroy(MsnSoapRequest *req) +{ + g_free(req->path); + msn_soap_message_destroy(req->message); + g_free(req); +} + +xmlnode * +msn_soap_xml_get(xmlnode *parent, const char *node) +{ + xmlnode *ret = NULL; + char **tokens = g_strsplit(node, "/", -1); + int i; + + for (i = 0; tokens[i]; i++) { + if ((ret = xmlnode_get_child(parent, tokens[i])) != NULL) + parent = ret; + else + break; + } + + g_strfreev(tokens); + return ret; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msn/soap2.h Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,58 @@ +/** + * @file soap2.h + * header file for SOAP connection related process + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _MSN_SOAP2_H +#define _MSN_SOAP2_H + +#include "session.h" +#include "sslconn.h" +#include "xmlnode.h" + +#include <glib.h> + +typedef struct _MsnSoapMessage MsnSoapMessage; +typedef void (*MsnSoapCallback)(MsnSoapMessage *request, + MsnSoapMessage *response, gpointer cb_data); + +struct _MsnSoapMessage { + char *action; + xmlnode *xml; + GSList *headers; +}; + +MsnSoapMessage *msn_soap_message_new(const char *action, xmlnode *xml); + +void msn_soap_message_add_header(MsnSoapMessage *req, + const char *name, const char *value); + +void msn_soap_message_send(MsnSession *session, + MsnSoapMessage *message, const char *host, const char *path, + MsnSoapCallback cb, gpointer cb_data); + +void msn_soap_message_destroy(MsnSoapMessage *message); + +xmlnode *msn_soap_xml_get(xmlnode *parent, const char *node); + +#endif
--- a/libpurple/protocols/msn/state.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/msn/state.c Sat Nov 17 02:20:01 2007 +0000 @@ -83,60 +83,56 @@ } /* parse CurrentMedia string */ -char * -msn_parse_currentmedia(const char *cmedia) +gboolean +msn_parse_currentmedia(const char *cmedia, CurrentMedia *media) { char **cmedia_array; - GString *buffer = NULL; int strings; if ((cmedia == NULL) || (*cmedia == '\0')) { purple_debug_info("msn", "No currentmedia string\n"); - return NULL; + return FALSE; } purple_debug_info("msn", "Parsing currentmedia string: \"%s\"\n", cmedia); cmedia_array = g_strsplit(cmedia, "\\0", 0); + /* + * 0: Media Player + * 1: 'Music' + * 2: '1' if enabled, '0' if not + * 3: Format (eg. {0} by {1}) + * 4: Title + * 5: Artist + * 6: Album + * 7: ? + */ strings = 0; - /* Yes, we want to skip the first element here, as it is empty due to - * the cmedia string starting with \0 -- see the examples below. */ while (cmedia_array[++strings] != NULL); - /* The cmedia_array[2] field contains a 1 if enabled. */ - if ((strings > 3) && (!strcmp(cmedia_array[2], "1"))) { - char *inptr = cmedia_array[3]; - - buffer = g_string_new(NULL); - - while (*inptr != '\0') { - if ((*inptr == '{') && ((*(inptr + 1) != '\0') && (*(inptr+2) == '}'))) { - char *tmpptr; - int tmp; - - errno = 0; - tmp = strtol(inptr + 1, &tmpptr, 10); + if (strings < 4) + return FALSE; + if (strcmp(cmedia_array[2], "1")) + return FALSE; - if (errno == 0 && tmpptr != inptr + 1 && - tmp + 4 < strings) { - /* Replace {?} tag with appropriate text only when successful. - * Skip otherwise. */ - buffer = g_string_append(buffer, cmedia_array[tmp + 4]); - } - inptr += 3; /* Skip to the next char after '}' */ - } else { - buffer = g_string_append_c(buffer, *inptr++); - } - } - purple_debug_info("msn", "Parsed currentmedia string, result: \"%s\"\n", - buffer->str); + if (strings == 4) { + media->title = g_strdup(cmedia_array[3]); } else { - purple_debug_info("msn", "Current media marked disabled, not parsing.\n"); + media->title = g_strdup(cmedia_array[4]); } - g_strfreev(cmedia_array); - return buffer ? g_string_free(buffer, FALSE) : NULL; + if (strings > 5) + media->artist = g_strdup(cmedia_array[5]); + else + media->artist = NULL; + + if (strings > 6) + media->album = g_strdup(cmedia_array[6]); + else + media->album = NULL; + + return TRUE; } /* get the CurrentMedia info from the XML string */ @@ -191,6 +187,27 @@ return psm; } +static char * +create_media_string(PurplePresence *presence) +{ + const char *artist, *title, *album; + char *ret; + PurpleStatus *status = purple_presence_get_status(presence, "tune"); + if (!status || !purple_status_is_active(status)) + return g_strdup_printf("WMP\\0Music\\00\\0{0} - {1}\\0\\0\\0\\0\\0"); + + artist = purple_status_get_attr_string(status, PURPLE_TUNE_ARTIST); + title = purple_status_get_attr_string(status, PURPLE_TUNE_TITLE); + album = purple_status_get_attr_string(status, PURPLE_TUNE_ALBUM); + + ret = g_strdup_printf("WMP\\0Music\\0%c\\0{0} - {1}\\0%s\\0%s\\0%s\\0\\0", + (title && *title) ? '1' : '0', + title ? title : "", + artist ? artist : "", + album ? album : ""); + return ret; +} + /* set the MSN's PSM info,Currently Read from the status Line * Thanks for Cris Code */ @@ -204,27 +221,28 @@ MsnTransaction *trans; char *payload; const char *statusline; - gchar *unescapedstatusline; + gchar *statusline_stripped, *media = NULL; g_return_if_fail(session != NULL); g_return_if_fail(session->notification != NULL); cmdproc = session->notification->cmdproc; - /*prepare PSM info*/ - if(session->psm){ - g_free(session->psm); - } - /*Get the PSM string from Purple's Status Line*/ + + /* Get the PSM string from Purple's Status Line */ presence = purple_account_get_presence(account); status = purple_presence_get_active_status(presence); statusline = purple_status_get_attr_string(status, "message"); - unescapedstatusline = purple_unescape_html(statusline); - session->psm = msn_build_psm(unescapedstatusline, NULL, NULL); - g_free(unescapedstatusline); + + /* MSN expects plain text, not HTML */ + statusline_stripped = purple_markup_strip_html(statusline); + media = create_media_string(presence); + g_free(session->psm); + session->psm = msn_build_psm(statusline_stripped, media, NULL); + g_free(statusline_stripped); + payload = session->psm; - purple_debug_misc("MSNP14","Sending UUX command with payload: %s\n",payload); - trans = msn_transaction_new(cmdproc, "UUX","%d",strlen(payload)); + trans = msn_transaction_new(cmdproc, "UUX", "%d", strlen(payload)); msn_transaction_set_payload(trans, payload, strlen(payload)); msn_cmdproc_send_trans(cmdproc, trans); }
--- a/libpurple/protocols/msn/state.h Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/msn/state.h Sat Nov 17 02:20:01 2007 +0000 @@ -62,7 +62,7 @@ void msn_set_psm(MsnSession *session); /* Parse CurrentMedia string */ -char * msn_parse_currentmedia(const char *cmedia); +gboolean msn_parse_currentmedia(const char *cmedia, CurrentMedia *media); /* Get the CurrentMedia info from the XML string */ char * msn_get_currentmedia(char *xml_str,gsize len);
--- a/libpurple/protocols/msn/switchboard.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/msn/switchboard.c Sat Nov 17 02:20:01 2007 +0000 @@ -108,8 +108,8 @@ g_free(swboard->auth_key); g_free(swboard->session_id); - for (l = swboard->users; l != NULL; l = l->next) - g_free(l->data); + for (; swboard->users; swboard->users = g_list_delete_link(swboard->users, swboard->users)) + g_free(swboard->users->data); session = swboard->session; session->switches = g_list_remove(session->switches, swboard); @@ -152,9 +152,7 @@ g_return_if_fail(swboard != NULL); g_return_if_fail(id != NULL); - if (swboard->session_id != NULL) - g_free(swboard->session_id); - + g_free(swboard->session_id); swboard->session_id = g_strdup(id); } @@ -735,10 +733,9 @@ msn_message_show_readable(msg, "SB RECV", FALSE); #endif - if (msg->remote_user != NULL) - g_free (msg->remote_user); + g_free (msg->remote_user); + msg->remote_user = g_strdup(cmd->params[0]); - msg->remote_user = g_strdup(cmd->params[0]); msn_cmdproc_process_msg(cmdproc, msg); msn_message_destroy(msg);
--- a/libpurple/protocols/msn/user.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/msn/user.c Sat Nov 17 02:20:01 2007 +0000 @@ -80,6 +80,9 @@ g_free(user->phone.home); g_free(user->phone.work); g_free(user->phone.mobile); + g_free(user->media.artist); + g_free(user->media.title); + g_free(user->media.album); g_free(user); } @@ -91,23 +94,24 @@ account = user->userlist->session->account; - if (user->statusline != NULL && user->currentmedia != NULL) { + if (user->status != NULL) { + gboolean offline = (strcmp(user->status, "offline") == 0); purple_prpl_got_user_status(account, user->passport, user->status, - "message", user->statusline, - "currentmedia", user->currentmedia, NULL); - } else if (user->currentmedia != NULL) { - purple_prpl_got_user_status(account, user->passport, user->status, "currentmedia", - user->currentmedia, NULL); - } else if (user->statusline != NULL) { - //char *status = g_strdup_printf("%s - %s", user->status, user->statusline); - purple_prpl_got_user_status(account, user->passport, user->status, - "message", user->statusline, NULL); - } else if (user->status != NULL) { - if (!strcmp(user->status, "offline") && user->mobile) { - purple_prpl_got_user_status(account, user->passport, "offline", NULL); + "message", user->statusline, NULL); + + if (!offline && user->media.title) { + purple_prpl_got_user_status(account, user->passport, "tune", + PURPLE_TUNE_ARTIST, user->media.artist, + PURPLE_TUNE_ALBUM, user->media.album, + PURPLE_TUNE_TITLE, user->media.title, + NULL); + } else { + purple_prpl_got_user_status_deactive(account, user->passport, "tune"); + } + + if (!offline && user->mobile) { purple_prpl_got_user_status(account, user->passport, "mobile", NULL); } else { - purple_prpl_got_user_status(account, user->passport, user->status, NULL); purple_prpl_got_user_status_deactive(account, user->passport, "mobile"); } } @@ -172,12 +176,17 @@ } void -msn_user_set_currentmedia(MsnUser *user, const char *currentmedia) +msn_user_set_currentmedia(MsnUser *user, const CurrentMedia *media) { g_return_if_fail(user != NULL); - g_free(user->currentmedia); - user->currentmedia = g_strdup(currentmedia); + g_free(user->media.title); + g_free(user->media.album); + g_free(user->media.artist); + + user->media.title = media ? g_strdup(media->title) : NULL; + user->media.artist = media ? g_strdup(media->artist) : NULL; + user->media.album = media ? g_strdup(media->album) : NULL; } void @@ -383,7 +392,7 @@ return; g_free(l->data); - user->group_ids = g_list_remove_link(user->group_ids, l); + user->group_ids = g_list_delete_link(user->group_ids, l); } void @@ -391,10 +400,8 @@ { g_return_if_fail(user != NULL); - if (user->phone.home != NULL) - g_free(user->phone.home); - - user->phone.home = (number == NULL ? NULL : g_strdup(number)); + g_free(user->phone.home); + user->phone.home = g_strdup(number); } void @@ -402,10 +409,8 @@ { g_return_if_fail(user != NULL); - if (user->phone.work != NULL) - g_free(user->phone.work); - - user->phone.work = (number == NULL ? NULL : g_strdup(number)); + g_free(user->phone.work); + user->phone.work = g_strdup(number); } void @@ -413,10 +418,8 @@ { g_return_if_fail(user != NULL); - if (user->phone.mobile != NULL) - g_free(user->phone.mobile); - - user->phone.mobile = (number == NULL ? NULL : g_strdup(number)); + g_free(user->phone.mobile); + user->phone.mobile = g_strdup(number); } void
--- a/libpurple/protocols/msn/user.h Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/msn/user.h Sat Nov 17 02:20:01 2007 +0000 @@ -43,6 +43,16 @@ } MsnUserType; /** + * Current media. + */ +typedef struct _CurrentMedia +{ + char *artist; /**< Artist. */ + char *album; /**< Album. */ + char *title; /**< Title. */ +} CurrentMedia; + +/** * A user. */ struct _MsnUser @@ -60,7 +70,7 @@ const char *status; /**< The state of the user. */ char *statusline; /**< The state of the user. */ - char *currentmedia; /**< The current media of the user. */ + CurrentMedia media; /**< Current media of the user. */ gboolean idle; /**< The idle state of the user. */ @@ -134,10 +144,10 @@ /** * Sets the current media of user. * - * @param user The user. - * @param state The statusline string. + * @param user The user. + * @param cmedia Current media. */ -void msn_user_set_currentmedia(MsnUser *user, const char *currentmedia); +void msn_user_set_currentmedia(MsnUser *user, const CurrentMedia *cmedia); /** * Sets the new state of user.
--- a/libpurple/protocols/msn/userlist.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/msn/userlist.c Sat Nov 17 02:20:01 2007 +0000 @@ -48,13 +48,10 @@ { MsnSession *session = pa->gc->proto_data; MsnUserList *userlist = session->userlist; - MsnUser *user = msn_userlist_find_add_user(userlist, pa->who, pa->who); - - + msn_userlist_add_buddy_to_list(userlist, pa->who, MSN_LIST_AL); - if (msn_userlist_user_is_in_list(user, MSN_LIST_FL)) - msn_del_contact_from_list(session->contact, NULL, pa->who, MSN_LIST_PL); + msn_del_contact_from_list(session->contact, NULL, pa->who, MSN_LIST_PL); } g_free(pa->who); @@ -73,7 +70,7 @@ { MsnSession *session = pa->gc->proto_data; MsnUserList *userlist = session->userlist; - MsnCallbackState *state = msn_callback_state_new(); + MsnCallbackState *state = msn_callback_state_new(session); msn_callback_state_set_action(state, MSN_DENIED_BUDDY); @@ -230,11 +227,8 @@ } else if (list_id == MSN_LIST_RL) { - PurpleConnection *gc; PurpleConversation *convo; - gc = purple_account_get_connection(account); - purple_debug_info("msn", "%s has added you to his or her buddy list.\n", passport); @@ -686,7 +680,7 @@ purple_debug_info("MSN Userlist", "Add user: %s to group: %s\n", who, new_group_name); - state = msn_callback_state_new(); + state = msn_callback_state_new(userlist->session); msn_callback_state_set_who(state, who); msn_callback_state_set_new_group_name(state, new_group_name); @@ -847,7 +841,7 @@ g_return_if_fail(userlist->session != NULL); g_return_if_fail(userlist->session->contact != NULL); - state = msn_callback_state_new(); + state = msn_callback_state_new(userlist->session); msn_callback_state_set_who(state, who); msn_callback_state_set_action(state, MSN_MOVE_BUDDY); msn_callback_state_set_old_group_name(state, old_group_name);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/Makefile.am Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,90 @@ +EXTRA_DIST = \ + Makefile.mingw + +pkgdir = $(libdir)/purple-$(PURPLE_MAJOR_VERSION) + +MSNP9SOURCES = \ + cmdproc.c \ + cmdproc.h \ + command.c \ + command.h \ + dialog.c \ + dialog.h \ + directconn.c \ + directconn.h \ + error.c \ + error.h \ + group.c \ + group.h \ + history.c \ + history.h \ + httpconn.c \ + httpconn.h \ + msg.c \ + msg.h \ + msn.c \ + msn.h \ + nexus.c \ + nexus.h \ + notification.c \ + notification.h \ + object.c \ + object.h \ + page.c \ + page.h \ + servconn.c \ + servconn.h \ + session.c \ + session.h \ + slp.c \ + slp.h \ + slpcall.c \ + slpcall.h \ + slplink.c \ + slplink.h \ + slpmsg.c \ + slpmsg.h \ + slpsession.c \ + slpsession.h \ + state.c \ + state.h \ + switchboard.c \ + switchboard.h \ + sync.c \ + sync.h \ + table.c \ + table.h \ + transaction.c \ + transaction.h \ + user.c \ + user.h \ + userlist.c \ + userlist.h \ + msn-utils.c \ + msn-utils.h + +AM_CFLAGS = $(st) + +libmsn_la_LDFLAGS = -module -avoid-version + +if STATIC_MSN + +st = -DPURPLE_STATIC_PRPL +noinst_LIBRARIES = libmsn.a +libmsn_a_SOURCES = $(MSNP9SOURCES) +libmsn_a_CFLAGS = $(AM_CFLAGS) + +else + +st = +pkg_LTLIBRARIES = libmsn.la +libmsn_la_SOURCES = $(MSNP9SOURCES) +libmsn_la_LIBADD = $(GLIB_LIBS) + +endif + +AM_CPPFLAGS = \ + -I$(top_srcdir)/libpurple \ + -I$(top_builddir)/libpurple \ + $(GLIB_CFLAGS) \ + $(DEBUG_CFLAGS)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/Makefile.mingw Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,105 @@ +# +# Makefile.mingw +# +# Description: Makefile for win32 (mingw) version of libmsn +# + +PIDGIN_TREE_TOP := ../../.. +include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak + +TARGET = libmsn +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 = cmdproc.c \ + command.c \ + dialog.c \ + directconn.c \ + error.c \ + group.c \ + history.c \ + httpconn.c \ + msg.c \ + msn.c \ + nexus.c \ + notification.c \ + object.c \ + page.c \ + servconn.c \ + session.c \ + slp.c \ + slpcall.c \ + slplink.c \ + slpmsg.c \ + slpsession.c \ + state.c \ + switchboard.c \ + sync.c \ + table.c \ + transaction.c \ + user.c \ + userlist.c \ + msn-utils.c + +OBJECTS = $(C_SRC:%.c=%.o) + +## +## LIBRARIES +## +LIBS = \ + -lglib-2.0 \ + -lintl \ + -lws2_32 \ + -lpurple + +include $(PIDGIN_COMMON_RULES) + +## +## TARGET DEFINITIONS +## +.PHONY: all install clean + +all: $(TARGET).dll + +install: all $(DLL_INSTALL_DIR) + cp $(TARGET).dll $(DLL_INSTALL_DIR) + +$(OBJECTS): $(PURPLE_CONFIG_H) + +$(TARGET).dll: $(PURPLE_DLL).a $(OBJECTS) + $(CC) -shared $(OBJECTS) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -o $(TARGET).dll + +## +## CLEAN RULES +## +clean: + rm -f $(OBJECTS) + rm -f $(TARGET).dll + +include $(PIDGIN_COMMON_TARGETS)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/cmdproc.c Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,336 @@ +/** + * @file cmdproc.c MSN command processor functions + * + * 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 "msn.h" +#include "cmdproc.h" + +MsnCmdProc * +msn_cmdproc_new(MsnSession *session) +{ + MsnCmdProc *cmdproc; + + cmdproc = g_new0(MsnCmdProc, 1); + + cmdproc->session = session; + cmdproc->txqueue = g_queue_new(); + cmdproc->history = msn_history_new(); + + return cmdproc; +} + +void +msn_cmdproc_destroy(MsnCmdProc *cmdproc) +{ + MsnTransaction *trans; + + while ((trans = g_queue_pop_head(cmdproc->txqueue)) != NULL) + msn_transaction_destroy(trans); + + g_queue_free(cmdproc->txqueue); + + msn_history_destroy(cmdproc->history); + + if (cmdproc->last_cmd != NULL) + msn_command_destroy(cmdproc->last_cmd); + + g_free(cmdproc); +} + +void +msn_cmdproc_process_queue(MsnCmdProc *cmdproc) +{ + MsnTransaction *trans; + + while ((trans = g_queue_pop_head(cmdproc->txqueue)) != NULL) + msn_cmdproc_send_trans(cmdproc, trans); +} + +void +msn_cmdproc_queue_trans(MsnCmdProc *cmdproc, MsnTransaction *trans) +{ + g_return_if_fail(cmdproc != NULL); + g_return_if_fail(trans != NULL); + + g_queue_push_tail(cmdproc->txqueue, trans); +} + +static void +show_debug_cmd(MsnCmdProc *cmdproc, gboolean incoming, const char *command) +{ + MsnServConn *servconn; + const char *names[] = { "NS", "SB" }; + char *show; + char tmp; + size_t len; + + servconn = cmdproc->servconn; + len = strlen(command); + show = g_strdup(command); + + tmp = (incoming) ? 'S' : 'C'; + + if ((show[len - 1] == '\n') && (show[len - 2] == '\r')) + { + show[len - 2] = '\0'; + } + + purple_debug_misc("msn", "%c: %s %03d: %s\n", tmp, + names[servconn->type], servconn->num, show); + + g_free(show); +} + +void +msn_cmdproc_send_trans(MsnCmdProc *cmdproc, MsnTransaction *trans) +{ + MsnServConn *servconn; + char *data; + size_t len; + + g_return_if_fail(cmdproc != NULL); + g_return_if_fail(trans != NULL); + + servconn = cmdproc->servconn; + + if (!servconn->connected) + return; + + msn_history_add(cmdproc->history, trans); + + data = msn_transaction_to_string(trans); + + len = strlen(data); + + show_debug_cmd(cmdproc, FALSE, data); + + if (trans->callbacks == NULL) + trans->callbacks = g_hash_table_lookup(cmdproc->cbs_table->cmds, + trans->command); + + if (trans->payload != NULL) + { + data = g_realloc(data, len + trans->payload_len); + memcpy(data + len, trans->payload, trans->payload_len); + len += trans->payload_len; + } + + msn_servconn_write(servconn, data, len); + + g_free(data); +} + +void +msn_cmdproc_send_quick(MsnCmdProc *cmdproc, const char *command, + const char *format, ...) +{ + MsnServConn *servconn; + char *data; + char *params = NULL; + va_list arg; + size_t len; + + g_return_if_fail(cmdproc != NULL); + g_return_if_fail(command != NULL); + + servconn = cmdproc->servconn; + + if (!servconn->connected) + return; + + if (format != NULL) + { + va_start(arg, format); + params = g_strdup_vprintf(format, arg); + va_end(arg); + } + + if (params != NULL) + data = g_strdup_printf("%s %s\r\n", command, params); + else + data = g_strdup_printf("%s\r\n", command); + + g_free(params); + + len = strlen(data); + + show_debug_cmd(cmdproc, FALSE, data); + + msn_servconn_write(servconn, data, len); + + g_free(data); +} + +void +msn_cmdproc_send(MsnCmdProc *cmdproc, const char *command, + const char *format, ...) +{ + MsnTransaction *trans; + va_list arg; + + g_return_if_fail(cmdproc != NULL); + g_return_if_fail(command != NULL); + + if (!cmdproc->servconn->connected) + return; + + trans = g_new0(MsnTransaction, 1); + + trans->command = g_strdup(command); + + if (format != NULL) + { + va_start(arg, format); + trans->params = g_strdup_vprintf(format, arg); + va_end(arg); + } + + msn_cmdproc_send_trans(cmdproc, trans); +} + +void +msn_cmdproc_process_payload(MsnCmdProc *cmdproc, char *payload, + int payload_len) +{ + MsnCommand *last; + + g_return_if_fail(cmdproc != NULL); + + last = cmdproc->last_cmd; + last->payload = g_memdup(payload, payload_len); + last->payload_len = payload_len; + + if (last->payload_cb != NULL) + last->payload_cb(cmdproc, last, payload, payload_len); +} + +void +msn_cmdproc_process_msg(MsnCmdProc *cmdproc, MsnMessage *msg) +{ + MsnMsgTypeCb cb; + + if (msn_message_get_content_type(msg) == NULL) + { + purple_debug_misc("msn", "failed to find message content\n"); + return; + } + + cb = g_hash_table_lookup(cmdproc->cbs_table->msgs, + msn_message_get_content_type(msg)); + + if (cb == NULL) + { + purple_debug_warning("msn", "Unhandled content-type '%s'\n", + msn_message_get_content_type(msg)); + + return; + } + + cb(cmdproc, msg); +} + +void +msn_cmdproc_process_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnTransCb cb = NULL; + MsnTransaction *trans = NULL; + + if (cmd->trId) + trans = msn_history_find(cmdproc->history, cmd->trId); + + if (trans != NULL) + if (trans->timer) + purple_timeout_remove(trans->timer); + + if (g_ascii_isdigit(cmd->command[0])) + { + if (trans != NULL) + { + MsnErrorCb error_cb = NULL; + int error; + + error = atoi(cmd->command); + + if (trans->error_cb != NULL) + error_cb = trans->error_cb; + + if (error_cb == NULL && cmdproc->cbs_table->errors != NULL) + error_cb = g_hash_table_lookup(cmdproc->cbs_table->errors, trans->command); + + if (error_cb != NULL) + { + error_cb(cmdproc, trans, error); + } + else + { +#if 1 + msn_error_handle(cmdproc->session, error); +#else + purple_debug_warning("msn", "Unhandled error '%s'\n", + cmd->command); +#endif + } + + return; + } + } + + if (cmdproc->cbs_table->async != NULL) + cb = g_hash_table_lookup(cmdproc->cbs_table->async, cmd->command); + + if (cb == NULL && trans != NULL) + { + cmd->trans = trans; + + if (trans->callbacks != NULL) + cb = g_hash_table_lookup(trans->callbacks, cmd->command); + } + + if (cb == NULL && cmdproc->cbs_table->fallback != NULL) + cb = g_hash_table_lookup(cmdproc->cbs_table->fallback, cmd->command); + + if (cb != NULL) + { + cb(cmdproc, cmd); + } + else + { + purple_debug_warning("msn", "Unhandled command '%s'\n", + cmd->command); + } + + if (trans != NULL && trans->pendent_cmd != NULL) + msn_transaction_unqueue_cmd(trans, cmdproc); +} + +void +msn_cmdproc_process_cmd_text(MsnCmdProc *cmdproc, const char *command) +{ + show_debug_cmd(cmdproc, TRUE, command); + + if (cmdproc->last_cmd != NULL) + msn_command_destroy(cmdproc->last_cmd); + + cmdproc->last_cmd = msn_command_from_string(command); + + msn_cmdproc_process_cmd(cmdproc, cmdproc->last_cmd); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/cmdproc.h Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,74 @@ +/** + * @file cmdproc.h MSN command processor functions + * + * 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 _MSN_CMDPROC_H_ +#define _MSN_CMDPROC_H_ + +typedef struct _MsnCmdProc MsnCmdProc; + +#include "session.h" +#include "servconn.h" +#include "error.h" +#include "command.h" +#include "table.h" +#include "history.h" + +struct _MsnCmdProc +{ + MsnSession *session; + MsnServConn *servconn; + + GQueue *txqueue; + + MsnCommand *last_cmd; + + MsnTable *cbs_table; + + MsnHistory *history; + + void *data; /**< Extra data, like the switchboard. */ +}; + +MsnCmdProc *msn_cmdproc_new(MsnSession *session); +void msn_cmdproc_destroy(MsnCmdProc *cmdproc); + +void msn_cmdproc_process_queue(MsnCmdProc *cmdproc); + +void msn_cmdproc_send_trans(MsnCmdProc *cmdproc, MsnTransaction *trans); +void msn_cmdproc_queue_trans(MsnCmdProc *cmdproc, + MsnTransaction *trans); +void msn_cmdproc_send(MsnCmdProc *cmdproc, const char *command, + const char *format, ...); +void msn_cmdproc_send_quick(MsnCmdProc *cmdproc, const char *command, + const char *format, ...); + +void msn_cmdproc_process_msg(MsnCmdProc *cmdproc, + MsnMessage *msg); +void msn_cmdproc_process_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd); +void msn_cmdproc_process_cmd_text(MsnCmdProc *cmdproc, const char *command); +void msn_cmdproc_process_payload(MsnCmdProc *cmdproc, + char *payload, int payload_len); + +void msn_cmdproc_disconnect(MsnCmdProc *cmdproc); + +#endif /* _MSN_CMDPROC_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/command.c Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,123 @@ +/** + * @file command.c MSN command functions + * + * 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 "msn.h" +#include "command.h" + +static gboolean +is_num(char *str) +{ + char *c; + for (c = str; *c; c++) { + if (!(g_ascii_isdigit(*c))) + return FALSE; + } + + return TRUE; +} + +MsnCommand * +msn_command_from_string(const char *string) +{ + MsnCommand *cmd; + char *tmp; + char *param_start; + + g_return_val_if_fail(string != NULL, NULL); + + tmp = g_strdup(string); + param_start = strchr(tmp, ' '); + + cmd = g_new0(MsnCommand, 1); + cmd->command = tmp; + + if (param_start) + { + *param_start++ = '\0'; + cmd->params = g_strsplit(param_start, " ", 0); + } + + if (cmd->params != NULL) + { + char *param; + int c; + + for (c = 0; cmd->params[c]; c++); + cmd->param_count = c; + + param = cmd->params[0]; + + cmd->trId = is_num(param) ? atoi(param) : 0; + } + else + cmd->trId = 0; + + msn_command_ref(cmd); + + return cmd; +} + +void +msn_command_destroy(MsnCommand *cmd) +{ + g_return_if_fail(cmd != NULL); + + if (cmd->ref_count > 0) + { + msn_command_unref(cmd); + return; + } + + if (cmd->payload != NULL) + g_free(cmd->payload); + + g_free(cmd->command); + g_strfreev(cmd->params); + g_free(cmd); +} + +MsnCommand * +msn_command_ref(MsnCommand *cmd) +{ + g_return_val_if_fail(cmd != NULL, NULL); + + cmd->ref_count++; + return cmd; +} + +MsnCommand * +msn_command_unref(MsnCommand *cmd) +{ + g_return_val_if_fail(cmd != NULL, NULL); + g_return_val_if_fail(cmd->ref_count > 0, NULL); + + cmd->ref_count--; + + if (cmd->ref_count == 0) + { + msn_command_destroy(cmd); + return NULL; + } + + return cmd; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/command.h Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,61 @@ +/** + * @file command.h MSN command functions + * + * 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 _MSN_COMMAND_H +#define _MSN_COMMAND_H + +typedef struct _MsnCommand MsnCommand; + +#include "cmdproc.h" +#include "transaction.h" + +typedef void (*MsnPayloadCb)(MsnCmdProc *cmdproc, MsnCommand *cmd, + char *payload, size_t len); + +/** + * A received command. + */ +struct _MsnCommand +{ + unsigned int trId; + + char *command; + char **params; + int param_count; + + int ref_count; + + MsnTransaction *trans; + + char *payload; + size_t payload_len; + + MsnPayloadCb payload_cb; +}; + +MsnCommand *msn_command_from_string(const char *string); +void msn_command_destroy(MsnCommand *cmd); +MsnCommand *msn_command_ref(MsnCommand *cmd); +MsnCommand *msn_command_unref(MsnCommand *cmd); + +#endif /* _MSN_COMMAND_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/dialog.c Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,138 @@ +/** + * @file dialog.c Dialog functions + * + * 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 "msn.h" +#include "dialog.h" + +typedef struct +{ + PurpleConnection *gc; + char *who; + char *group; + gboolean add; + +} MsnAddRemData; + +/* Remove the buddy referenced by the MsnAddRemData before the serverside list is changed. + * If the buddy will be added, he'll be added back; if he will be removed, he won't be. */ +static void +msn_complete_sync_issue(MsnAddRemData *data) +{ + PurpleBuddy *buddy; + PurpleGroup *group = NULL; + + if (data->group != NULL) + group = purple_find_group(data->group); + + if (group != NULL) + buddy = purple_find_buddy_in_group(purple_connection_get_account(data->gc), data->who, group); + else + buddy = purple_find_buddy(purple_connection_get_account(data->gc), data->who); + + if (buddy != NULL) + purple_blist_remove_buddy(buddy); +} + +static void +msn_add_cb(MsnAddRemData *data) +{ + MsnSession *session; + MsnUserList *userlist; + + msn_complete_sync_issue(data); + + session = data->gc->proto_data; + userlist = session->userlist; + + msn_userlist_add_buddy(userlist, data->who, MSN_LIST_FL, data->group); + + g_free(data->group); + g_free(data->who); + g_free(data); +} + +static void +msn_rem_cb(MsnAddRemData *data) +{ + MsnSession *session; + MsnUserList *userlist; + + msn_complete_sync_issue(data); + + session = data->gc->proto_data; + userlist = session->userlist; + + msn_userlist_rem_buddy(userlist, data->who, MSN_LIST_FL, data->group); + + g_free(data->group); + g_free(data->who); + g_free(data); +} + +void +msn_show_sync_issue(MsnSession *session, const char *passport, + const char *group_name) +{ + PurpleConnection *gc; + PurpleAccount *account; + MsnAddRemData *data; + char *msg, *reason; + + account = session->account; + gc = purple_account_get_connection(account); + + data = g_new0(MsnAddRemData, 1); + data->who = g_strdup(passport); + data->group = g_strdup(group_name); + data->gc = gc; + + msg = g_strdup_printf(_("Buddy list synchronization issue in %s (%s)"), + purple_account_get_username(account), + purple_account_get_protocol_name(account)); + + if (group_name != NULL) + { + reason = g_strdup_printf(_("%s on the local list is " + "inside the group \"%s\" but not on " + "the server list. " + "Do you want this buddy to be added?"), + passport, group_name); + } + else + { + reason = g_strdup_printf(_("%s is on the local list but " + "not on the server list. " + "Do you want this buddy to be added?"), + passport); + } + + purple_request_action(gc, NULL, msg, reason, PURPLE_DEFAULT_ACTION_NONE, + purple_connection_get_account(gc), data->who, NULL, + data, 2, + _("Yes"), G_CALLBACK(msn_add_cb), + _("No"), G_CALLBACK(msn_rem_cb)); + + g_free(reason); + g_free(msg); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/dialog.h Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,30 @@ +/** + * @file dialog.h Dialog functions + * + * 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 _MSN_DIALOG_H_ +#define _MSN_DIALOG_H_ + +void msn_show_sync_issue(MsnSession *session, const char *passport, + const char *group_name); + +#endif /* _MSN_DIALOG_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/directconn.c Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,494 @@ +/** + * @file directconn.c MSN direct connection functions + * + * 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 "msn.h" +#include "directconn.h" + +#include "slp.h" +#include "slpmsg.h" + +/************************************************************************** + * Directconn Specific + **************************************************************************/ + +void +msn_directconn_send_handshake(MsnDirectConn *directconn) +{ + MsnSlpLink *slplink; + MsnSlpMessage *slpmsg; + + g_return_if_fail(directconn != NULL); + + slplink = directconn->slplink; + + slpmsg = msn_slpmsg_new(slplink); + slpmsg->flags = 0x100; + + if (directconn->nonce != NULL) + { + guint32 t1; + guint16 t2; + guint16 t3; + guint16 t4; + guint64 t5; + + sscanf (directconn->nonce, "%08X-%04hX-%04hX-%04hX-%012" G_GINT64_MODIFIER "X", &t1, &t2, &t3, &t4, &t5); + + t1 = GUINT32_TO_LE(t1); + t2 = GUINT16_TO_LE(t2); + t3 = GUINT16_TO_LE(t3); + t4 = GUINT16_TO_BE(t4); + t5 = GUINT64_TO_BE(t5); + + slpmsg->ack_id = t1; + slpmsg->ack_sub_id = t2 | (t3 << 16); + slpmsg->ack_size = t4 | t5; + } + + g_free(directconn->nonce); + + msn_slplink_send_slpmsg(slplink, slpmsg); + + directconn->acked =TRUE; +} + +/************************************************************************** + * Connection Functions + **************************************************************************/ + +#if 0 +static int +create_listener(int port) +{ + int fd; + const int on = 1; + +#if 0 + struct addrinfo hints; + struct addrinfo *c, *res; + char port_str[5]; + + snprintf(port_str, sizeof(port_str), "%d", port); + + memset(&hints, 0, sizeof(hints)); + + hints.ai_flags = AI_PASSIVE; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + if (getaddrinfo(NULL, port_str, &hints, &res) != 0) + { + purple_debug_error("msn", "Could not get address info: %s.\n", + port_str); + return -1; + } + + for (c = res; c != NULL; c = c->ai_next) + { + fd = socket(c->ai_family, c->ai_socktype, c->ai_protocol); + + if (fd < 0) + continue; + + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + + if (bind(fd, c->ai_addr, c->ai_addrlen) == 0) + break; + + close(fd); + } + + if (c == NULL) + { + purple_debug_error("msn", "Could not find socket: %s.\n", port_str); + return -1; + } + + freeaddrinfo(res); +#else + struct sockaddr_in sockin; + + fd = socket(AF_INET, SOCK_STREAM, 0); + + if (fd < 0) + return -1; + + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) != 0) + { + close(fd); + return -1; + } + + memset(&sockin, 0, sizeof(struct sockaddr_in)); + sockin.sin_family = AF_INET; + sockin.sin_port = htons(port); + + if (bind(fd, (struct sockaddr *)&sockin, sizeof(struct sockaddr_in)) != 0) + { + close(fd); + return -1; + } +#endif + + if (listen (fd, 4) != 0) + { + close (fd); + return -1; + } + + fcntl(fd, F_SETFL, O_NONBLOCK); + + return fd; +} +#endif + +static size_t +msn_directconn_write(MsnDirectConn *directconn, + const char *data, size_t len) +{ + char *buffer, *tmp; + size_t buf_size; + size_t ret; + guint32 sent_len; + + g_return_val_if_fail(directconn != NULL, 0); + + buf_size = len + 4; + buffer = tmp = g_malloc(buf_size); + + sent_len = GUINT32_TO_LE(len); + + memcpy(tmp, &sent_len, 4); + tmp += 4; + memcpy(tmp, data, len); + tmp += len; + + ret = write(directconn->fd, buffer, buf_size); + +#ifdef DEBUG_DC + char *str; + str = g_strdup_printf("%s/msntest/w%.4d.bin", g_get_home_dir(), directconn->c); + + FILE *tf = g_fopen(str, "w"); + fwrite(buffer, 1, buf_size, tf); + fclose(tf); + + g_free(str); +#endif + + g_free(buffer); + +#if 0 + /* Let's write the length of the data. */ + ret = write(directconn->fd, &len, sizeof(len)); + + /* Let's write the data. */ + ret = write(directconn->fd, data, len); + + char *str; + str = g_strdup_printf("/home/revo/msntest/w%.4d.bin", directconn->c); + + FILE *tf = g_fopen(str, "w"); + fwrite(&len, 1, sizeof(len), tf); + fwrite(data, 1, len, tf); + fclose(tf); + + g_free(str); +#endif + + directconn->c++; + + return ret; +} + +#if 0 +void +msn_directconn_parse_nonce(MsnDirectConn *directconn, const char *nonce) +{ + guint32 t1; + guint16 t2; + guint16 t3; + guint16 t4; + guint64 t5; + + g_return_if_fail(directconn != NULL); + g_return_if_fail(nonce != NULL); + + sscanf (nonce, "%08X-%04hX-%04hX-%04hX-%012llX", &t1, &t2, &t3, &t4, &t5); + + t1 = GUINT32_TO_LE(t1); + t2 = GUINT16_TO_LE(t2); + t3 = GUINT16_TO_LE(t3); + t4 = GUINT16_TO_BE(t4); + t5 = GUINT64_TO_BE(t5); + + directconn->slpheader = g_new0(MsnSlpHeader, 1); + + directconn->slpheader->ack_id = t1; + directconn->slpheader->ack_sub_id = t2 | (t3 << 16); + directconn->slpheader->ack_size = t4 | t5; +} +#endif + +void +msn_directconn_send_msg(MsnDirectConn *directconn, MsnMessage *msg) +{ + char *body; + size_t body_len; + + body = msn_message_gen_slp_body(msg, &body_len); + + msn_directconn_write(directconn, body, body_len); +} + +static void +msn_directconn_process_msg(MsnDirectConn *directconn, MsnMessage *msg) +{ + purple_debug_info("msn", "directconn: process_msg\n"); + + msn_slplink_process_msg(directconn->slplink, msg); +} + +static void +read_cb(gpointer data, gint source, PurpleInputCondition cond) +{ + MsnDirectConn* directconn; + char *body; + size_t len, body_len; + + purple_debug_info("msn", "read_cb: %d, %d\n", source, cond); + + directconn = data; + + /* Let's read the length of the data. */ + len = read(directconn->fd, &body_len, sizeof(body_len)); + + if (len <= 0) + { + /* ERROR */ + purple_debug_error("msn", "error reading\n"); + + msn_directconn_destroy(directconn); + + return; + } + + body_len = GUINT32_FROM_LE(body_len); + + purple_debug_info("msn", "body_len=%d\n", body_len); + + if (body_len <= 0) + { + /* ERROR */ + purple_debug_error("msn", "error reading\n"); + + msn_directconn_destroy(directconn); + + return; + } + + body = g_try_malloc(body_len); + + if (body != NULL) + { + /* Let's read the data. */ + len = read(directconn->fd, body, body_len); + + purple_debug_info("msn", "len=%d\n", len); + } + else + { + purple_debug_error("msn", "Failed to allocate memory for read\n"); + len = 0; + } + + if (len > 0) + { + MsnMessage *msg; + +#ifdef DEBUG_DC + str = g_strdup_printf("/home/revo/msntest/r%.4d.bin", directconn->c); + + FILE *tf = g_fopen(str, "w"); + fwrite(body, 1, len, tf); + fclose(tf); + + g_free(str); +#endif + + directconn->c++; + + msg = msn_message_new_msnslp(); + msn_message_parse_slp_body(msg, body, body_len); + + msn_directconn_process_msg(directconn, msg); + } + else + { + /* ERROR */ + purple_debug_error("msn", "error reading\n"); + + msn_directconn_destroy(directconn); + } +} + +static void +connect_cb(gpointer data, gint source, const gchar *error_message) +{ + MsnDirectConn* directconn; + int fd; + + purple_debug_misc("msn", "directconn: connect_cb: %d\n", source); + + directconn = data; + directconn->connect_data = NULL; + + if (TRUE) + { + fd = source; + } + else + { + struct sockaddr_in client_addr; + socklen_t client; + fd = accept (source, (struct sockaddr *)&client_addr, &client); + } + + directconn->fd = fd; + + if (fd > 0) + { + directconn->inpa = purple_input_add(fd, PURPLE_INPUT_READ, read_cb, + directconn); + + if (TRUE) + { + /* Send foo. */ + msn_directconn_write(directconn, "foo", strlen("foo") + 1); + + /* Send Handshake */ + msn_directconn_send_handshake(directconn); + } + else + { + } + } + else + { + /* ERROR */ + purple_debug_error("msn", "could not add input\n"); + + if (directconn->inpa) + purple_input_remove(directconn->inpa); + + close(directconn->fd); + } +} + +gboolean +msn_directconn_connect(MsnDirectConn *directconn, const char *host, int port) +{ + MsnSession *session; + + g_return_val_if_fail(directconn != NULL, FALSE); + g_return_val_if_fail(host != NULL, TRUE); + g_return_val_if_fail(port > 0, FALSE); + + session = directconn->slplink->session; + +#if 0 + if (session->http_method) + { + servconn->http_data->gateway_host = g_strdup(host); + } +#endif + + directconn->connect_data = purple_proxy_connect(NULL, session->account, + host, port, connect_cb, directconn); + + if (directconn->connect_data != NULL) + { + return TRUE; + } + else + return FALSE; +} + +#if 0 +void +msn_directconn_listen(MsnDirectConn *directconn) +{ + int port; + int fd; + + port = 7000; + + for (fd = -1; fd < 0;) + fd = create_listener(++port); + + directconn->fd = fd; + + directconn->inpa = purple_input_add(fd, PURPLE_INPUT_READ, connect_cb, + directconn); + + directconn->port = port; + directconn->c = 0; +} +#endif + +MsnDirectConn* +msn_directconn_new(MsnSlpLink *slplink) +{ + MsnDirectConn *directconn; + + directconn = g_new0(MsnDirectConn, 1); + + directconn->slplink = slplink; + + if (slplink->directconn != NULL) + purple_debug_info("msn", "got_transresp: LEAK\n"); + + slplink->directconn = directconn; + + return directconn; +} + +void +msn_directconn_destroy(MsnDirectConn *directconn) +{ + if (directconn->connect_data != NULL) + purple_proxy_connect_cancel(directconn->connect_data); + + if (directconn->inpa != 0) + purple_input_remove(directconn->inpa); + + if (directconn->fd >= 0) + close(directconn->fd); + + if (directconn->nonce != NULL) + g_free(directconn->nonce); + + directconn->slplink->directconn = NULL; + + g_free(directconn); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/directconn.h Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,63 @@ +/** + * @file directconn.h MSN direct connection functions + * + * 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 _MSN_DIRECTCONN_H_ +#define _MSN_DIRECTCONN_H_ + +typedef struct _MsnDirectConn MsnDirectConn; + +#include "slplink.h" +#include "slp.h" +#include "msg.h" + +struct _MsnDirectConn +{ + MsnSlpLink *slplink; + MsnSlpCall *initial_call; + + PurpleProxyConnectData *connect_data; + + gboolean acked; + + char *nonce; + + int fd; + + int port; + int inpa; + + int c; +}; + +MsnDirectConn *msn_directconn_new(MsnSlpLink *slplink); +gboolean msn_directconn_connect(MsnDirectConn *directconn, + const char *host, int port); +#if 0 +void msn_directconn_listen(MsnDirectConn *directconn); +#endif +void msn_directconn_send_msg(MsnDirectConn *directconn, MsnMessage *msg); +void msn_directconn_parse_nonce(MsnDirectConn *directconn, const char *nonce); +void msn_directconn_destroy(MsnDirectConn *directconn); +void msn_directconn_send_handshake(MsnDirectConn *directconn); + +#endif /* _MSN_DIRECTCONN_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/error.c Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,269 @@ +/** + * @file error.c Error functions + * + * 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 "msn.h" +#include "error.h" + +const char * +msn_error_get_text(unsigned int type, gboolean *debug) +{ + static char msg[MSN_BUF_LEN]; + *debug = FALSE; + + switch (type) { + case 0: + g_snprintf(msg, sizeof(msg), + _("Unable to parse message")); + *debug = TRUE; + break; + case 200: + g_snprintf(msg, sizeof(msg), + _("Syntax Error (probably a client bug)")); + *debug = TRUE; + break; + case 201: + g_snprintf(msg, sizeof(msg), + _("Invalid e-mail address")); + break; + case 205: + g_snprintf(msg, sizeof(msg), _("User does not exist")); + break; + case 206: + g_snprintf(msg, sizeof(msg), + _("Fully qualified domain name missing")); + break; + case 207: + g_snprintf(msg, sizeof(msg), _("Already logged in")); + break; + case 208: + g_snprintf(msg, sizeof(msg), _("Invalid screen name")); + break; + case 209: + g_snprintf(msg, sizeof(msg), _("Invalid friendly name")); + break; + case 210: + g_snprintf(msg, sizeof(msg), _("List full")); + break; + case 215: + g_snprintf(msg, sizeof(msg), _("Already there")); + *debug = TRUE; + break; + case 216: + g_snprintf(msg, sizeof(msg), _("Not on list")); + break; + case 217: + g_snprintf(msg, sizeof(msg), _("User is offline")); + break; + case 218: + g_snprintf(msg, sizeof(msg), _("Already in the mode")); + *debug = TRUE; + break; + case 219: + g_snprintf(msg, sizeof(msg), _("Already in opposite list")); + *debug = TRUE; + break; + case 223: + g_snprintf(msg, sizeof(msg), _("Too many groups")); + break; + case 224: + g_snprintf(msg, sizeof(msg), _("Invalid group")); + break; + case 225: + g_snprintf(msg, sizeof(msg), _("User not in group")); + break; + case 229: + g_snprintf(msg, sizeof(msg), _("Group name too long")); + break; + case 230: + g_snprintf(msg, sizeof(msg), _("Cannot remove group zero")); + *debug = TRUE; + break; + case 231: + g_snprintf(msg, sizeof(msg), + _("Tried to add a user to a group " + "that doesn't exist")); + break; + case 280: + g_snprintf(msg, sizeof(msg), _("Switchboard failed")); + *debug = TRUE; + break; + case 281: + g_snprintf(msg, sizeof(msg), _("Notify transfer failed")); + *debug = TRUE; + break; + + case 300: + g_snprintf(msg, sizeof(msg), _("Required fields missing")); + *debug = TRUE; + break; + case 301: + g_snprintf(msg, sizeof(msg), _("Too many hits to a FND")); + *debug = TRUE; + break; + case 302: + g_snprintf(msg, sizeof(msg), _("Not logged in")); + break; + + case 500: + g_snprintf(msg, sizeof(msg), _("Service temporarily unavailable")); + break; + case 501: + g_snprintf(msg, sizeof(msg), _("Database server error")); + *debug = TRUE; + break; + case 502: + g_snprintf(msg, sizeof(msg), _("Command disabled")); + *debug = TRUE; + break; + case 510: + g_snprintf(msg, sizeof(msg), _("File operation error")); + *debug = TRUE; + break; + case 520: + g_snprintf(msg, sizeof(msg), _("Memory allocation error")); + *debug = TRUE; + break; + case 540: + g_snprintf(msg, sizeof(msg), _("Wrong CHL value sent to server")); + *debug = TRUE; + break; + + case 600: + g_snprintf(msg, sizeof(msg), _("Server busy")); + break; + case 601: + g_snprintf(msg, sizeof(msg), _("Server unavailable")); + break; + case 602: + g_snprintf(msg, sizeof(msg), _("Peer notification server down")); + *debug = TRUE; + break; + case 603: + g_snprintf(msg, sizeof(msg), _("Database connect error")); + *debug = TRUE; + break; + case 604: + g_snprintf(msg, sizeof(msg), + _("Server is going down (abandon ship)")); + break; + case 605: + g_snprintf(msg, sizeof(msg), _("Server unavailable")); + break; + + case 707: + g_snprintf(msg, sizeof(msg), _("Error creating connection")); + *debug = TRUE; + break; + case 710: + g_snprintf(msg, sizeof(msg), + _("CVR parameters are either unknown or not allowed")); + *debug = TRUE; + break; + case 711: + g_snprintf(msg, sizeof(msg), _("Unable to write")); + break; + case 712: + g_snprintf(msg, sizeof(msg), _("Session overload")); + *debug = TRUE; + break; + case 713: + g_snprintf(msg, sizeof(msg), _("User is too active")); + break; + case 714: + g_snprintf(msg, sizeof(msg), _("Too many sessions")); + break; + case 715: + g_snprintf(msg, sizeof(msg), _("Passport not verified")); + break; + case 717: + g_snprintf(msg, sizeof(msg), _("Bad friend file")); + *debug = TRUE; + break; + case 731: + g_snprintf(msg, sizeof(msg), _("Not expected")); + *debug = TRUE; + break; + + case 800: + g_snprintf(msg, sizeof(msg), + _("Friendly name changes too rapidly")); + break; + + case 910: + case 912: + case 918: + case 919: + case 921: + case 922: + g_snprintf(msg, sizeof(msg), _("Server too busy")); + break; + case 911: + case 917: + g_snprintf(msg, sizeof(msg), _("Authentication failed")); + break; + case 913: + g_snprintf(msg, sizeof(msg), _("Not allowed when offline")); + break; + case 914: + case 915: + case 916: + g_snprintf(msg, sizeof(msg), _("Server unavailable")); + break; + case 920: + g_snprintf(msg, sizeof(msg), _("Not accepting new users")); + break; + case 923: + g_snprintf(msg, sizeof(msg), + _("Kids Passport without parental consent")); + break; + case 924: + g_snprintf(msg, sizeof(msg), + _("Passport account not yet verified")); + break; + case 928: + g_snprintf(msg, sizeof(msg), _("Bad ticket")); + *debug = TRUE; + break; + + default: + g_snprintf(msg, sizeof(msg), _("Unknown Error Code %d"), type); + *debug = TRUE; + break; + } + + return msg; +} + +void +msn_error_handle(MsnSession *session, unsigned int type) +{ + char buf[MSN_BUF_LEN]; + gboolean debug; + + g_snprintf(buf, sizeof(buf), _("MSN Error: %s\n"), + msn_error_get_text(type, &debug)); + if (debug) + purple_debug_warning("msn", "error %d: %s\n", type, buf); + else + purple_notify_error(session->account->gc, NULL, buf, NULL); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/error.h Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,47 @@ +/** + * @file error.h Error functions + * + * 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 _MSN_ERROR_H_ +#define _MSN_ERROR_H_ + +#include "session.h" + +/** + * Returns the string representation of an error type. + * + * @param type The error type. + * @param debug Whether this should be treated as a debug log message or a user-visible error + * + * @return The string representation of the error type. + */ +const char *msn_error_get_text(unsigned int type, gboolean *debug); + +/** + * Handles an error. + * + * @param session The current session. + * @param type The error type. + */ +void msn_error_handle(MsnSession *session, unsigned int type); + +#endif /* _MSN_ERROR_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/group.c Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,89 @@ +/** + * @file group.c Group functions + * + * 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 "msn.h" +#include "group.h" + +MsnGroup * +msn_group_new(MsnUserList *userlist, int id, const char *name) +{ + MsnGroup *group; + + g_return_val_if_fail(id >= 0, NULL); + g_return_val_if_fail(name != NULL, NULL); + + group = g_new0(MsnGroup, 1); + + msn_userlist_add_group(userlist, group); + + group->id = id; + group->name = g_strdup(name); + + return group; +} + +void +msn_group_destroy(MsnGroup *group) +{ + g_return_if_fail(group != NULL); + + g_free(group->name); + g_free(group); +} + +void +msn_group_set_id(MsnGroup *group, int id) +{ + g_return_if_fail(group != NULL); + g_return_if_fail(id >= 0); + + group->id = id; +} + +void +msn_group_set_name(MsnGroup *group, const char *name) +{ + g_return_if_fail(group != NULL); + g_return_if_fail(name != NULL); + + if (group->name != NULL) + g_free(group->name); + + group->name = g_strdup(name); +} + +int +msn_group_get_id(const MsnGroup *group) +{ + g_return_val_if_fail(group != NULL, -1); + + return group->id; +} + +const char * +msn_group_get_name(const MsnGroup *group) +{ + g_return_val_if_fail(group != NULL, NULL); + + return group->name; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/group.h Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,103 @@ +/** + * @file group.h Group functions + * + * 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 _MSN_GROUP_H_ +#define _MSN_GROUP_H_ + +typedef struct _MsnGroup MsnGroup; + +#include <stdio.h> + +#include "session.h" +#include "user.h" + +#include "userlist.h" + +/** + * A group. + */ +struct _MsnGroup +{ + MsnSession *session; /**< The MSN session. */ + + int id; /**< The group ID. */ + char *name; /**< The name of the group. */ +}; + +/**************************************************************************/ +/** @name Group API */ +/**************************************************************************/ +/*@{*/ + +/** + * Creates a new group structure. + * + * @param session The MSN session. + * @param id The group ID. + * @param name The name of the group. + * + * @return A new group structure. + */ +MsnGroup *msn_group_new(MsnUserList *userlist, int id, const char *name); + +/** + * Destroys a group structure. + * + * @param group The group to destroy. + */ +void msn_group_destroy(MsnGroup *group); + +/** + * Sets the ID for a group. + * + * @param group The group. + * @param id The ID. + */ +void msn_group_set_id(MsnGroup *group, int id); + +/** + * Sets the name for a group. + * + * @param group The group. + * @param name The name. + */ +void msn_group_set_name(MsnGroup *group, const char *name); + +/** + * Returns the ID for a group. + * + * @param group The group. + * + * @return The ID. + */ +int msn_group_get_id(const MsnGroup *group); + +/** + * Returns the name for a group. + * + * @param group The group. + * + * @return The name. + */ +const char *msn_group_get_name(const MsnGroup *group); +#endif /* _MSN_GROUP_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/history.c Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,86 @@ +/** + * @file history.c MSN history functions + * + * 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 "msn.h" +#include "history.h" + +MsnHistory * +msn_history_new(void) +{ + MsnHistory *history = g_new0(MsnHistory, 1); + + history->trId = 1; + + history->queue = g_queue_new(); + + return history; +} + +void +msn_history_destroy(MsnHistory *history) +{ + MsnTransaction *trans; + + while ((trans = g_queue_pop_head(history->queue)) != NULL) + msn_transaction_destroy(trans); + + g_queue_free(history->queue); + g_free(history); +} + +MsnTransaction * +msn_history_find(MsnHistory *history, unsigned int trId) +{ + MsnTransaction *trans; + GList *list; + + for (list = history->queue->head; list != NULL; list = list->next) + { + trans = list->data; + if (trans->trId == trId) + return trans; + } + + return NULL; +} + +void +msn_history_add(MsnHistory *history, MsnTransaction *trans) +{ + GQueue *queue; + + g_return_if_fail(history != NULL); + g_return_if_fail(trans != NULL); + + queue = history->queue; + + trans->trId = history->trId++; + + g_queue_push_tail(queue, trans); + + if (queue->length > MSN_HIST_ELEMS) + { + trans = g_queue_pop_head(queue); + msn_transaction_destroy(trans); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/history.h Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,47 @@ +/** + * @file history.h MSN history functions + * + * 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 _MSN_HISTORY_H +#define _MSN_HISTORY_H + +#define MSN_HIST_ELEMS 0x30 + +typedef struct _MsnHistory MsnHistory; + +#include "transaction.h" + +/** + * The history. + */ +struct _MsnHistory +{ + GQueue *queue; + unsigned int trId; +}; + +MsnHistory *msn_history_new(void); +void msn_history_destroy(MsnHistory *history); +MsnTransaction *msn_history_find(MsnHistory *history, unsigned int triId); +void msn_history_add(MsnHistory *history, MsnTransaction *trans); + +#endif /* _MSN_HISTORY_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/httpconn.c Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,775 @@ +/** + * @file httpmethod.c HTTP connection method + * + * 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 "msn.h" +#include "debug.h" +#include "httpconn.h" + +typedef struct +{ + MsnHttpConn *httpconn; + char *body; + size_t body_len; +} MsnHttpQueueData; + +static void +msn_httpconn_process_queue(MsnHttpConn *httpconn) +{ + httpconn->waiting_response = FALSE; + + if (httpconn->queue != NULL) + { + MsnHttpQueueData *queue_data; + + queue_data = (MsnHttpQueueData *)httpconn->queue->data; + + httpconn->queue = g_list_remove(httpconn->queue, queue_data); + + msn_httpconn_write(queue_data->httpconn, + queue_data->body, + queue_data->body_len); + + g_free(queue_data->body); + g_free(queue_data); + } +} + +static gboolean +msn_httpconn_parse_data(MsnHttpConn *httpconn, const char *buf, + size_t size, char **ret_buf, size_t *ret_size, + gboolean *error) +{ + const char *s, *c; + char *header, *body; + const char *body_start; + char *tmp; + size_t body_len = 0; + gboolean wasted = FALSE; + + g_return_val_if_fail(httpconn != NULL, FALSE); + g_return_val_if_fail(buf != NULL, FALSE); + g_return_val_if_fail(size > 0, FALSE); + g_return_val_if_fail(ret_buf != NULL, FALSE); + g_return_val_if_fail(ret_size != NULL, FALSE); + g_return_val_if_fail(error != NULL, FALSE); + +#if 0 + purple_debug_info("msn", "HTTP: parsing data {%s}\n", buf); +#endif + + /* Healthy defaults. */ + body = NULL; + + *ret_buf = NULL; + *ret_size = 0; + *error = FALSE; + + /* First, some tests to see if we have a full block of stuff. */ + if (((strncmp(buf, "HTTP/1.1 200 OK\r\n", 17) != 0) && + (strncmp(buf, "HTTP/1.1 100 Continue\r\n", 23) != 0)) && + ((strncmp(buf, "HTTP/1.0 200 OK\r\n", 17) != 0) && + (strncmp(buf, "HTTP/1.0 100 Continue\r\n", 23) != 0))) + { + *error = TRUE; + + return FALSE; + } + + if (strncmp(buf, "HTTP/1.1 100 Continue\r\n", 23) == 0) + { + if ((s = strstr(buf, "\r\n\r\n")) == NULL) + return FALSE; + + s += 4; + + if (*s == '\0') + { + *ret_buf = g_strdup(""); + *ret_size = 0; + + msn_httpconn_process_queue(httpconn); + + return TRUE; + } + + buf = s; + size -= (s - buf); + } + + if ((s = strstr(buf, "\r\n\r\n")) == NULL) + /* Need to wait for the full HTTP header to arrive */ + return FALSE; + + s += 4; /* Skip \r\n */ + header = g_strndup(buf, s - buf); + body_start = s; + body_len = size - (body_start - buf); + + if ((s = purple_strcasestr(header, "Content-Length: ")) != NULL) + { + int tmp_len; + + s += strlen("Content-Length: "); + + if ((c = strchr(s, '\r')) == NULL) + { + g_free(header); + + return FALSE; + } + + tmp = g_strndup(s, c - s); + tmp_len = atoi(tmp); + g_free(tmp); + + if (body_len != tmp_len) + { + /* Need to wait for the full packet to arrive */ + + g_free(header); + +#if 0 + purple_debug_warning("msn", + "body length (%d) != content length (%d)\n", + body_len, tmp_len); +#endif + + return FALSE; + } + } + + body = g_malloc0(body_len + 1); + memcpy(body, body_start, body_len); + +#ifdef MSN_DEBUG_HTTP + purple_debug_misc("msn", "Incoming HTTP buffer (header): {%s\r\n}\n", + header); +#endif + + /* Now we should be able to process the data. */ + if ((s = purple_strcasestr(header, "X-MSN-Messenger: ")) != NULL) + { + char *full_session_id, *gw_ip, *session_action; + char *t, *session_id; + char **elems, **cur, **tokens; + + full_session_id = gw_ip = session_action = NULL; + + s += strlen("X-MSN-Messenger: "); + + if ((c = strchr(s, '\r')) == NULL) + { + msn_session_set_error(httpconn->session, + MSN_ERROR_HTTP_MALFORMED, NULL); + purple_debug_error("msn", "Malformed X-MSN-Messenger field.\n{%s}\n", + buf); + + g_free(body); + return FALSE; + } + + tmp = g_strndup(s, c - s); + + elems = g_strsplit(tmp, "; ", 0); + + for (cur = elems; *cur != NULL; cur++) + { + tokens = g_strsplit(*cur, "=", 2); + + if (strcmp(tokens[0], "SessionID") == 0) + full_session_id = tokens[1]; + else if (strcmp(tokens[0], "GW-IP") == 0) + gw_ip = tokens[1]; + else if (strcmp(tokens[0], "Session") == 0) + session_action = tokens[1]; + else + g_free(tokens[1]); + + g_free(tokens[0]); + /* Don't free each of the tokens, only the array. */ + g_free(tokens); + } + + g_strfreev(elems); + + g_free(tmp); + + if ((session_action != NULL) && (strcmp(session_action, "close") == 0)) + wasted = TRUE; + + g_free(session_action); + + t = strchr(full_session_id, '.'); + session_id = g_strndup(full_session_id, t - full_session_id); + + if (!wasted) + { + g_free(httpconn->full_session_id); + httpconn->full_session_id = full_session_id; + + g_free(httpconn->session_id); + httpconn->session_id = session_id; + + g_free(httpconn->host); + httpconn->host = gw_ip; + } + else + { + MsnServConn *servconn; + + /* It's going to die. */ + /* poor thing */ + + servconn = httpconn->servconn; + + /* I'll be honest, I don't fully understand all this, but this + * causes crashes, Stu. */ + /* if (servconn != NULL) + servconn->wasted = TRUE; */ + + g_free(full_session_id); + g_free(session_id); + g_free(gw_ip); + } + } + + g_free(header); + + *ret_buf = body; + *ret_size = body_len; + + msn_httpconn_process_queue(httpconn); + + return TRUE; +} + +static void +read_cb(gpointer data, gint source, PurpleInputCondition cond) +{ + MsnHttpConn *httpconn; + MsnServConn *servconn; + MsnSession *session; + char buf[MSN_BUF_LEN]; + char *cur, *end, *old_rx_buf; + int len, cur_len; + char *result_msg = NULL; + size_t result_len = 0; + gboolean error = FALSE; + + httpconn = data; + servconn = NULL; + session = httpconn->session; + + len = read(httpconn->fd, buf, sizeof(buf) - 1); + + if (len < 0 && errno == EAGAIN) + return; + else if (len <= 0) + { + purple_debug_error("msn", "HTTP: Read error\n"); + msn_servconn_got_error(httpconn->servconn, MSN_SERVCONN_ERROR_READ); + + return; + } + + buf[len] = '\0'; + + httpconn->rx_buf = g_realloc(httpconn->rx_buf, len + httpconn->rx_len + 1); + memcpy(httpconn->rx_buf + httpconn->rx_len, buf, len + 1); + httpconn->rx_len += len; + + if (!msn_httpconn_parse_data(httpconn, httpconn->rx_buf, httpconn->rx_len, + &result_msg, &result_len, &error)) + { + /* Either we must wait for more input, or something went wrong */ + if (error) + msn_servconn_got_error(httpconn->servconn, MSN_SERVCONN_ERROR_READ); + + return; + } + + httpconn->servconn->processing = FALSE; + + servconn = httpconn->servconn; + + if (error) + { + purple_debug_error("msn", "HTTP: Special error\n"); + msn_servconn_got_error(httpconn->servconn, MSN_SERVCONN_ERROR_READ); + + return; + } + + g_free(httpconn->rx_buf); + httpconn->rx_buf = NULL; + httpconn->rx_len = 0; + + if (result_len == 0) + { + /* Nothing to do here */ +#if 0 + purple_debug_info("msn", "HTTP: nothing to do here\n"); +#endif + g_free(result_msg); + return; + } + + g_free(servconn->rx_buf); + servconn->rx_buf = result_msg; + servconn->rx_len = result_len; + + end = old_rx_buf = servconn->rx_buf; + + servconn->processing = TRUE; + + do + { + cur = end; + + if (servconn->payload_len) + { + if (servconn->payload_len > servconn->rx_len) + /* The payload is still not complete. */ + break; + + cur_len = servconn->payload_len; + end += cur_len; + } + else + { + end = strstr(cur, "\r\n"); + + if (end == NULL) + /* The command is still not complete. */ + break; + + *end = '\0'; + end += 2; + cur_len = end - cur; + } + + servconn->rx_len -= cur_len; + + if (servconn->payload_len) + { + msn_cmdproc_process_payload(servconn->cmdproc, cur, cur_len); + servconn->payload_len = 0; + } + else + { + msn_cmdproc_process_cmd_text(servconn->cmdproc, cur); + } + } while (servconn->connected && servconn->rx_len > 0); + + if (servconn->connected) + { + if (servconn->rx_len > 0) + servconn->rx_buf = g_memdup(cur, servconn->rx_len); + else + servconn->rx_buf = NULL; + } + + servconn->processing = FALSE; + + if (servconn->wasted) + msn_servconn_destroy(servconn); + + g_free(old_rx_buf); +} + +static void +httpconn_write_cb(gpointer data, gint source, PurpleInputCondition cond) +{ + MsnHttpConn *httpconn; + int ret, writelen; + + httpconn = data; + writelen = purple_circ_buffer_get_max_read(httpconn->tx_buf); + + if (writelen == 0) + { + purple_input_remove(httpconn->tx_handler); + httpconn->tx_handler = 0; + return; + } + + ret = write(httpconn->fd, httpconn->tx_buf->outptr, writelen); + if (ret <= 0) + { + if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) + /* No worries */ + return; + + /* Error! */ + msn_servconn_got_error(httpconn->servconn, MSN_SERVCONN_ERROR_WRITE); + return; + } + + purple_circ_buffer_mark_read(httpconn->tx_buf, ret); + + /* TODO: I don't think these 2 lines are needed. Remove them? */ + if (ret == writelen) + httpconn_write_cb(data, source, cond); +} + +static gboolean +write_raw(MsnHttpConn *httpconn, const char *data, size_t data_len) +{ + ssize_t res; /* result of the write operation */ + + if (httpconn->tx_handler == 0) + res = write(httpconn->fd, data, data_len); + else + { + res = -1; + errno = EAGAIN; + } + + if ((res <= 0) && ((errno != EAGAIN) && (errno != EWOULDBLOCK))) + { + msn_servconn_got_error(httpconn->servconn, MSN_SERVCONN_ERROR_WRITE); + return FALSE; + } + + if (res < 0 || res < data_len) + { + if (res < 0) + res = 0; + if (httpconn->tx_handler == 0 && httpconn->fd) + httpconn->tx_handler = purple_input_add(httpconn->fd, + PURPLE_INPUT_WRITE, httpconn_write_cb, httpconn); + purple_circ_buffer_append(httpconn->tx_buf, data + res, + data_len - res); + } + + return TRUE; +} + +static char * +msn_httpconn_proxy_auth(MsnHttpConn *httpconn) +{ + PurpleAccount *account; + PurpleProxyInfo *gpi; + const char *username, *password; + char *auth = NULL; + + account = httpconn->session->account; + + gpi = purple_proxy_get_setup(account); + + if (gpi == NULL || !(purple_proxy_info_get_type(gpi) == PURPLE_PROXY_HTTP || + purple_proxy_info_get_type(gpi) == PURPLE_PROXY_USE_ENVVAR)) + return NULL; + + username = purple_proxy_info_get_username(gpi); + password = purple_proxy_info_get_password(gpi); + + if (username != NULL) { + char *tmp; + auth = g_strdup_printf("%s:%s", username, password ? password : ""); + tmp = purple_base64_encode((const guchar *)auth, strlen(auth)); + g_free(auth); + auth = g_strdup_printf("Proxy-Authorization: Basic %s\r\n", tmp); + g_free(tmp); + } + + return auth; +} + +static gboolean +msn_httpconn_poll(gpointer data) +{ + MsnHttpConn *httpconn; + char *header; + char *auth; + + httpconn = data; + + g_return_val_if_fail(httpconn != NULL, FALSE); + + if ((httpconn->host == NULL) || (httpconn->full_session_id == NULL)) + { + /* There's no need to poll if the session is not fully established */ + return TRUE; + } + + if (httpconn->waiting_response) + { + /* There's no need to poll if we're already waiting for a response */ + return TRUE; + } + + auth = msn_httpconn_proxy_auth(httpconn); + + header = g_strdup_printf( + "POST http://%s/gateway/gateway.dll?Action=poll&SessionID=%s HTTP/1.1\r\n" + "Accept: */*\r\n" + "Accept-Language: en-us\r\n" + "User-Agent: MSMSGS\r\n" + "Host: %s\r\n" + "Proxy-Connection: Keep-Alive\r\n" + "%s" /* Proxy auth */ + "Connection: Keep-Alive\r\n" + "Pragma: no-cache\r\n" + "Content-Type: application/x-msn-messenger\r\n" + "Content-Length: 0\r\n\r\n", + httpconn->host, + httpconn->full_session_id, + httpconn->host, + auth ? auth : ""); + + g_free(auth); + + if (write_raw(httpconn, header, strlen(header))) + httpconn->waiting_response = TRUE; + + g_free(header); + + return TRUE; +} + +ssize_t +msn_httpconn_write(MsnHttpConn *httpconn, const char *body, size_t body_len) +{ + char *params; + char *data; + int header_len; + char *auth; + const char *server_types[] = { "NS", "SB" }; + const char *server_type; + char *host; + MsnServConn *servconn; + + /* TODO: remove http data from servconn */ + + g_return_val_if_fail(httpconn != NULL, 0); + g_return_val_if_fail(body != NULL, 0); + g_return_val_if_fail(body_len > 0, 0); + + servconn = httpconn->servconn; + + if (httpconn->waiting_response) + { + MsnHttpQueueData *queue_data = g_new0(MsnHttpQueueData, 1); + + queue_data->httpconn = httpconn; + queue_data->body = g_memdup(body, body_len); + queue_data->body_len = body_len; + + httpconn->queue = g_list_append(httpconn->queue, queue_data); + + return body_len; + } + + server_type = server_types[servconn->type]; + + if (httpconn->virgin) + { + host = "gateway.messenger.hotmail.com"; + + /* The first time servconn->host is the host we should connect to. */ + params = g_strdup_printf("Action=open&Server=%s&IP=%s", + server_type, + servconn->host); + httpconn->virgin = FALSE; + } + else + { + /* The rest of the times servconn->host is the gateway host. */ + host = httpconn->host; + + if (host == NULL || httpconn->full_session_id == NULL) + { + purple_debug_warning("msn", "Attempted HTTP write before session is established\n"); + return -1; + } + + params = g_strdup_printf("SessionID=%s", + httpconn->full_session_id); + } + + auth = msn_httpconn_proxy_auth(httpconn); + + data = g_strdup_printf( + "POST http://%s/gateway/gateway.dll?%s HTTP/1.1\r\n" + "Accept: */*\r\n" + "Accept-Language: en-us\r\n" + "User-Agent: MSMSGS\r\n" + "Host: %s\r\n" + "Proxy-Connection: Keep-Alive\r\n" + "%s" /* Proxy auth */ + "Connection: Keep-Alive\r\n" + "Pragma: no-cache\r\n" + "Content-Type: application/x-msn-messenger\r\n" + "Content-Length: %d\r\n\r\n", + host, + params, + host, + auth ? auth : "", + (int) body_len); + + g_free(params); + + g_free(auth); + + header_len = strlen(data); + data = g_realloc(data, header_len + body_len); + memcpy(data + header_len, body, body_len); + + if (write_raw(httpconn, data, header_len + body_len)) + httpconn->waiting_response = TRUE; + + g_free(data); + + return body_len; +} + +MsnHttpConn * +msn_httpconn_new(MsnServConn *servconn) +{ + MsnHttpConn *httpconn; + + g_return_val_if_fail(servconn != NULL, NULL); + + httpconn = g_new0(MsnHttpConn, 1); + + purple_debug_info("msn", "new httpconn (%p)\n", httpconn); + + /* TODO: Remove this */ + httpconn->session = servconn->session; + + httpconn->servconn = servconn; + + httpconn->tx_buf = purple_circ_buffer_new(MSN_BUF_LEN); + httpconn->tx_handler = 0; + + return httpconn; +} + +void +msn_httpconn_destroy(MsnHttpConn *httpconn) +{ + g_return_if_fail(httpconn != NULL); + + purple_debug_info("msn", "destroy httpconn (%p)\n", httpconn); + + if (httpconn->connected) + msn_httpconn_disconnect(httpconn); + + g_free(httpconn->full_session_id); + + g_free(httpconn->session_id); + + g_free(httpconn->host); + + purple_circ_buffer_destroy(httpconn->tx_buf); + if (httpconn->tx_handler > 0) + purple_input_remove(httpconn->tx_handler); + + g_free(httpconn); +} + +static void +connect_cb(gpointer data, gint source, const gchar *error_message) +{ + MsnHttpConn *httpconn; + + httpconn = data; + httpconn->connect_data = NULL; + httpconn->fd = source; + + if (source >= 0) + { + httpconn->inpa = purple_input_add(httpconn->fd, PURPLE_INPUT_READ, + read_cb, data); + + httpconn->timer = purple_timeout_add(2000, msn_httpconn_poll, httpconn); + + msn_httpconn_process_queue(httpconn); + } + else + { + purple_debug_error("msn", "HTTP: Connection error\n"); + msn_servconn_got_error(httpconn->servconn, MSN_SERVCONN_ERROR_CONNECT); + } +} + +gboolean +msn_httpconn_connect(MsnHttpConn *httpconn, const char *host, int port) +{ + g_return_val_if_fail(httpconn != NULL, FALSE); + g_return_val_if_fail(host != NULL, FALSE); + g_return_val_if_fail(port > 0, FALSE); + + if (httpconn->connected) + msn_httpconn_disconnect(httpconn); + + httpconn->connect_data = purple_proxy_connect(NULL, httpconn->session->account, + host, 80, connect_cb, httpconn); + + if (httpconn->connect_data != NULL) + { + httpconn->waiting_response = TRUE; + httpconn->connected = TRUE; + } + + return httpconn->connected; +} + +void +msn_httpconn_disconnect(MsnHttpConn *httpconn) +{ + g_return_if_fail(httpconn != NULL); + + if (!httpconn->connected) + return; + + if (httpconn->connect_data != NULL) + { + purple_proxy_connect_cancel(httpconn->connect_data); + httpconn->connect_data = NULL; + } + + if (httpconn->timer) + { + purple_timeout_remove(httpconn->timer); + httpconn->timer = 0; + } + + if (httpconn->inpa > 0) + { + purple_input_remove(httpconn->inpa); + httpconn->inpa = 0; + } + + close(httpconn->fd); + httpconn->fd = -1; + + g_free(httpconn->rx_buf); + httpconn->rx_buf = NULL; + httpconn->rx_len = 0; + + httpconn->connected = FALSE; + + /* msn_servconn_disconnect(httpconn->servconn); */ +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/httpconn.h Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,111 @@ +/** + * @file httpconn.h HTTP connection + * + * 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 _MSN_HTTPCONN_H_ +#define _MSN_HTTPCONN_H_ + +typedef struct _MsnHttpConn MsnHttpConn; + +#include "circbuffer.h" +#include "servconn.h" + +/** + * An HTTP Connection. + */ +struct _MsnHttpConn +{ + MsnSession *session; /**< The MSN Session. */ + MsnServConn *servconn; /**< The connection object. */ + + PurpleProxyConnectData *connect_data; + + char *full_session_id; /**< The full session id. */ + char *session_id; /**< The trimmed session id. */ + + int timer; /**< The timer for polling. */ + + gboolean waiting_response; /**< The flag that states if we are waiting + a response from the server. */ + gboolean connected; /**< The flag that states if the connection is on. */ + gboolean virgin; /**< The flag that states if this connection + should specify the host (not gateway) to + connect to. */ + + char *host; /**< The HTTP gateway host. */ + GList *queue; /**< The queue of data chunks to write. */ + + int fd; /**< The connection's file descriptor. */ + guint inpa; /**< The connection's input handler. */ + + char *rx_buf; /**< The receive buffer. */ + int rx_len; /**< The receive buffer length. */ + + PurpleCircBuffer *tx_buf; + guint tx_handler; +}; + +/** + * Creates a new HTTP connection object. + * + * @param servconn The connection object. + * + * @return The new object. + */ +MsnHttpConn *msn_httpconn_new(MsnServConn *servconn); + +/** + * Destroys an HTTP connection object. + * + * @param httpconn The HTTP connection object. + */ +void msn_httpconn_destroy(MsnHttpConn *httpconn); + +/** + * Writes a chunk of data to the HTTP connection. + * + * @param servconn The server connection. + * @param data The data to write. + * @param data_len The size of the data to write. + * + * @return The number of bytes written. + */ +ssize_t msn_httpconn_write(MsnHttpConn *httpconn, const char *data, size_t data_len); + +/** + * Connects the HTTP connection object to a host. + * + * @param httpconn The HTTP connection object. + * @param host The host to connect to. + * @param port The port to connect to. + */ +gboolean msn_httpconn_connect(MsnHttpConn *httpconn, + const char *host, int port); + +/** + * Disconnects the HTTP connection object. + * + * @param httpconn The HTTP connection object. + */ +void msn_httpconn_disconnect(MsnHttpConn *httpconn); + +#endif /* _MSN_HTTPCONN_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/msg.c Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,787 @@ +/** + * @file msg.c Message functions + * + * 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 "msn.h" +#include "msg.h" + +MsnMessage * +msn_message_new(MsnMsgType type) +{ + MsnMessage *msg; + + msg = g_new0(MsnMessage, 1); + msg->type = type; + +#ifdef MSN_DEBUG_MSG + purple_debug_info("msn", "message new (%p)(%d)\n", msg, type); +#endif + + msg->attr_table = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, g_free); + + msn_message_ref(msg); + + return msg; +} + +void +msn_message_destroy(MsnMessage *msg) +{ + g_return_if_fail(msg != NULL); + + if (msg->ref_count > 0) + { + msn_message_unref(msg); + + return; + } + +#ifdef MSN_DEBUG_MSG + purple_debug_info("msn", "message destroy (%p)\n", msg); +#endif + + if (msg->remote_user != NULL) + g_free(msg->remote_user); + + if (msg->body != NULL) + g_free(msg->body); + + if (msg->content_type != NULL) + g_free(msg->content_type); + + if (msg->charset != NULL) + g_free(msg->charset); + + g_hash_table_destroy(msg->attr_table); + g_list_free(msg->attr_list); + + g_free(msg); +} + +MsnMessage * +msn_message_ref(MsnMessage *msg) +{ + g_return_val_if_fail(msg != NULL, NULL); + + msg->ref_count++; + +#ifdef MSN_DEBUG_MSG + purple_debug_info("msn", "message ref (%p)[%d]\n", msg, msg->ref_count); +#endif + + return msg; +} + +MsnMessage * +msn_message_unref(MsnMessage *msg) +{ + g_return_val_if_fail(msg != NULL, NULL); + g_return_val_if_fail(msg->ref_count > 0, NULL); + + msg->ref_count--; + +#ifdef MSN_DEBUG_MSG + purple_debug_info("msn", "message unref (%p)[%d]\n", msg, msg->ref_count); +#endif + + if (msg->ref_count == 0) + { + msn_message_destroy(msg); + + return NULL; + } + + return msg; +} + +MsnMessage * +msn_message_new_plain(const char *message) +{ + MsnMessage *msg; + char *message_cr; + + msg = msn_message_new(MSN_MSG_TEXT); + msn_message_set_attr(msg, "User-Agent", PACKAGE_NAME "/" DISPLAY_VERSION); + msn_message_set_content_type(msg, "text/plain"); + msn_message_set_charset(msg, "UTF-8"); + msn_message_set_flag(msg, 'A'); + msn_message_set_attr(msg, "X-MMS-IM-Format", + "FN=MS%20Sans%20Serif; EF=; CO=0; PF=0"); + + message_cr = purple_str_add_cr(message); + msn_message_set_bin_data(msg, message_cr, strlen(message_cr)); + g_free(message_cr); + + return msg; +} + +MsnMessage * +msn_message_new_msnslp(void) +{ + MsnMessage *msg; + + msg = msn_message_new(MSN_MSG_SLP); + + msn_message_set_attr(msg, "User-Agent", NULL); + + msg->msnslp_message = TRUE; + + msn_message_set_flag(msg, 'D'); + msn_message_set_content_type(msg, "application/x-msnmsgrp2p"); + + return msg; +} + +MsnMessage * +msn_message_new_nudge(void) +{ + MsnMessage *msg; + + msg = msn_message_new(MSN_MSG_NUDGE); + msn_message_set_content_type(msg, "text/x-msnmsgr-datacast\r\n"); + msn_message_set_flag(msg, 'N'); + msn_message_set_attr(msg,"ID","1\r\n"); + + return msg; +} + +void +msn_message_parse_slp_body(MsnMessage *msg, const char *body, size_t len) +{ + MsnSlpHeader header; + const char *tmp; + int body_len; + + tmp = body; + + if (len < sizeof(header)) { + g_return_if_reached(); + } + + /* Import the header. */ + memcpy(&header, tmp, sizeof(header)); + tmp += sizeof(header); + + msg->msnslp_header.session_id = GUINT32_FROM_LE(header.session_id); + msg->msnslp_header.id = GUINT32_FROM_LE(header.id); + msg->msnslp_header.offset = GUINT64_FROM_LE(header.offset); + msg->msnslp_header.total_size = GUINT64_FROM_LE(header.total_size); + msg->msnslp_header.length = GUINT32_FROM_LE(header.length); + msg->msnslp_header.flags = GUINT32_FROM_LE(header.flags); + msg->msnslp_header.ack_id = GUINT32_FROM_LE(header.ack_id); + msg->msnslp_header.ack_sub_id = GUINT32_FROM_LE(header.ack_sub_id); + msg->msnslp_header.ack_size = GUINT64_FROM_LE(header.ack_size); + + /* Import the body. */ + body_len = len - (tmp - body); + /* msg->body_len = msg->msnslp_header.length; */ + + if (body_len > 0) { + msg->body_len = len - (tmp - body); + msg->body = g_malloc0(msg->body_len + 1); + memcpy(msg->body, tmp, msg->body_len); + tmp += body_len; + } +} + +void +msn_message_parse_payload(MsnMessage *msg, + const char *payload, size_t payload_len) +{ + char *tmp_base, *tmp; + const char *content_type; + char *end; + char **elems, **cur, **tokens; + + g_return_if_fail(payload != NULL); + + tmp_base = tmp = g_malloc0(payload_len + 1); + memcpy(tmp_base, payload, payload_len); + + /* Parse the attributes. */ + end = strstr(tmp, "\r\n\r\n"); + /* TODO? some clients use \r delimiters instead of \r\n, the official client + * doesn't send such messages, but does handle receiving them. We'll just + * avoid crashing for now */ + if (end == NULL) { + g_free(tmp_base); + g_return_if_reached(); + } + *end = '\0'; + + elems = g_strsplit(tmp, "\r\n", 0); + + for (cur = elems; *cur != NULL; cur++) + { + const char *key, *value; + + tokens = g_strsplit(*cur, ": ", 2); + + key = tokens[0]; + value = tokens[1]; + + if (!strcmp(key, "MIME-Version")) + { + g_strfreev(tokens); + continue; + } + + if (!strcmp(key, "Content-Type")) + { + char *charset, *c; + + if ((c = strchr(value, ';')) != NULL) + { + if ((charset = strchr(c, '=')) != NULL) + { + charset++; + msn_message_set_charset(msg, charset); + } + + *c = '\0'; + } + + msn_message_set_content_type(msg, value); + } + else + { + msn_message_set_attr(msg, key, value); + } + + g_strfreev(tokens); + } + + g_strfreev(elems); + + /* Proceed to the end of the "\r\n\r\n" */ + tmp = end + 4; + + /* Now we *should* be at the body. */ + content_type = msn_message_get_content_type(msg); + + if (content_type != NULL && + !strcmp(content_type, "application/x-msnmsgrp2p")) + { + MsnSlpHeader header; + MsnSlpFooter footer; + int body_len; + + if (payload_len - (tmp - tmp_base) < sizeof(header)) { + g_free(tmp_base); + g_return_if_reached(); + } + + msg->msnslp_message = TRUE; + + /* Import the header. */ + memcpy(&header, tmp, sizeof(header)); + tmp += sizeof(header); + + msg->msnslp_header.session_id = GUINT32_FROM_LE(header.session_id); + msg->msnslp_header.id = GUINT32_FROM_LE(header.id); + msg->msnslp_header.offset = GUINT64_FROM_LE(header.offset); + msg->msnslp_header.total_size = GUINT64_FROM_LE(header.total_size); + msg->msnslp_header.length = GUINT32_FROM_LE(header.length); + msg->msnslp_header.flags = GUINT32_FROM_LE(header.flags); + msg->msnslp_header.ack_id = GUINT32_FROM_LE(header.ack_id); + msg->msnslp_header.ack_sub_id = GUINT32_FROM_LE(header.ack_sub_id); + msg->msnslp_header.ack_size = GUINT64_FROM_LE(header.ack_size); + + body_len = payload_len - (tmp - tmp_base) - sizeof(footer); + + /* Import the body. */ + if (body_len > 0) { + msg->body_len = body_len; + msg->body = g_malloc0(msg->body_len + 1); + memcpy(msg->body, tmp, msg->body_len); + tmp += body_len; + } + + /* Import the footer. */ + if (body_len >= 0) { + memcpy(&footer, tmp, sizeof(footer)); + tmp += sizeof(footer); + msg->msnslp_footer.value = GUINT32_FROM_BE(footer.value); + } + } + else + { + if (payload_len - (tmp - tmp_base) > 0) { + msg->body_len = payload_len - (tmp - tmp_base); + msg->body = g_malloc0(msg->body_len + 1); + memcpy(msg->body, tmp, msg->body_len); + } + } + + g_free(tmp_base); +} + +MsnMessage * +msn_message_new_from_cmd(MsnSession *session, MsnCommand *cmd) +{ + MsnMessage *msg; + + g_return_val_if_fail(cmd != NULL, NULL); + + msg = msn_message_new(MSN_MSG_UNKNOWN); + + msg->remote_user = g_strdup(cmd->params[0]); + /* msg->size = atoi(cmd->params[2]); */ + msg->cmd = cmd; + + return msg; +} + +char * +msn_message_gen_slp_body(MsnMessage *msg, size_t *ret_size) +{ + MsnSlpHeader header; + + char *tmp, *base; + const void *body; + size_t len, body_len; + + g_return_val_if_fail(msg != NULL, NULL); + + len = MSN_BUF_LEN; + + base = tmp = g_malloc(len + 1); + + body = msn_message_get_bin_data(msg, &body_len); + + header.session_id = GUINT32_TO_LE(msg->msnslp_header.session_id); + header.id = GUINT32_TO_LE(msg->msnslp_header.id); + header.offset = GUINT64_TO_LE(msg->msnslp_header.offset); + header.total_size = GUINT64_TO_LE(msg->msnslp_header.total_size); + header.length = GUINT32_TO_LE(msg->msnslp_header.length); + header.flags = GUINT32_TO_LE(msg->msnslp_header.flags); + header.ack_id = GUINT32_TO_LE(msg->msnslp_header.ack_id); + header.ack_sub_id = GUINT32_TO_LE(msg->msnslp_header.ack_sub_id); + header.ack_size = GUINT64_TO_LE(msg->msnslp_header.ack_size); + + memcpy(tmp, &header, 48); + tmp += 48; + + if (body != NULL) + { + memcpy(tmp, body, body_len); + tmp += body_len; + } + + if (ret_size != NULL) + *ret_size = tmp - base; + + return base; +} + +char * +msn_message_gen_payload(MsnMessage *msg, size_t *ret_size) +{ + GList *l; + char *n, *base, *end; + int len; + size_t body_len = 0; + const void *body; + + g_return_val_if_fail(msg != NULL, NULL); + + len = MSN_BUF_LEN; + + base = n = end = g_malloc(len + 1); + end += len; + + /* Standard header. */ + if (msg->charset == NULL) + { + g_snprintf(n, len, + "MIME-Version: 1.0\r\n" + "Content-Type: %s\r\n", + msg->content_type); + } + else + { + g_snprintf(n, len, + "MIME-Version: 1.0\r\n" + "Content-Type: %s; charset=%s\r\n", + msg->content_type, msg->charset); + } + + n += strlen(n); + + for (l = msg->attr_list; l != NULL; l = l->next) + { + const char *key; + const char *value; + + key = l->data; + value = msn_message_get_attr(msg, key); + + g_snprintf(n, end - n, "%s: %s\r\n", key, value); + n += strlen(n); + } + + n += g_strlcpy(n, "\r\n", end - n); + + body = msn_message_get_bin_data(msg, &body_len); + + if (msg->msnslp_message) + { + MsnSlpHeader header; + MsnSlpFooter footer; + + header.session_id = GUINT32_TO_LE(msg->msnslp_header.session_id); + header.id = GUINT32_TO_LE(msg->msnslp_header.id); + header.offset = GUINT64_TO_LE(msg->msnslp_header.offset); + header.total_size = GUINT64_TO_LE(msg->msnslp_header.total_size); + header.length = GUINT32_TO_LE(msg->msnslp_header.length); + header.flags = GUINT32_TO_LE(msg->msnslp_header.flags); + header.ack_id = GUINT32_TO_LE(msg->msnslp_header.ack_id); + header.ack_sub_id = GUINT32_TO_LE(msg->msnslp_header.ack_sub_id); + header.ack_size = GUINT64_TO_LE(msg->msnslp_header.ack_size); + + memcpy(n, &header, 48); + n += 48; + + if (body != NULL) + { + memcpy(n, body, body_len); + + n += body_len; + } + + footer.value = GUINT32_TO_BE(msg->msnslp_footer.value); + + memcpy(n, &footer, 4); + n += 4; + } + else + { + if (body != NULL) + { + memcpy(n, body, body_len); + n += body_len; + } + } + + if (ret_size != NULL) + { + *ret_size = n - base; + + if (*ret_size > 1664) + *ret_size = 1664; + } + + return base; +} + +void +msn_message_set_flag(MsnMessage *msg, char flag) +{ + g_return_if_fail(msg != NULL); + g_return_if_fail(flag != 0); + + msg->flag = flag; +} + +char +msn_message_get_flag(const MsnMessage *msg) +{ + g_return_val_if_fail(msg != NULL, 0); + + return msg->flag; +} + +void +msn_message_set_bin_data(MsnMessage *msg, const void *data, size_t len) +{ + g_return_if_fail(msg != NULL); + + /* There is no need to waste memory on data we cannot send anyway */ + if (len > 1664) + len = 1664; + + if (msg->body != NULL) + g_free(msg->body); + + if (data != NULL && len > 0) + { + msg->body = g_malloc0(len + 1); + memcpy(msg->body, data, len); + msg->body_len = len; + } + else + { + msg->body = NULL; + msg->body_len = 0; + } +} + +const void * +msn_message_get_bin_data(const MsnMessage *msg, size_t *len) +{ + g_return_val_if_fail(msg != NULL, NULL); + + if (len) + *len = msg->body_len; + + return msg->body; +} + +void +msn_message_set_content_type(MsnMessage *msg, const char *type) +{ + g_return_if_fail(msg != NULL); + + if (msg->content_type != NULL) + g_free(msg->content_type); + + msg->content_type = (type != NULL) ? g_strdup(type) : NULL; +} + +const char * +msn_message_get_content_type(const MsnMessage *msg) +{ + g_return_val_if_fail(msg != NULL, NULL); + + return msg->content_type; +} + +void +msn_message_set_charset(MsnMessage *msg, const char *charset) +{ + g_return_if_fail(msg != NULL); + + if (msg->charset != NULL) + g_free(msg->charset); + + msg->charset = (charset != NULL) ? g_strdup(charset) : NULL; +} + +const char * +msn_message_get_charset(const MsnMessage *msg) +{ + g_return_val_if_fail(msg != NULL, NULL); + + return msg->charset; +} + +void +msn_message_set_attr(MsnMessage *msg, const char *attr, const char *value) +{ + const char *temp; + char *new_attr; + + g_return_if_fail(msg != NULL); + g_return_if_fail(attr != NULL); + + temp = msn_message_get_attr(msg, attr); + + if (value == NULL) + { + if (temp != NULL) + { + GList *l; + + for (l = msg->attr_list; l != NULL; l = l->next) + { + if (!g_ascii_strcasecmp(l->data, attr)) + { + msg->attr_list = g_list_remove(msg->attr_list, l->data); + + break; + } + } + + g_hash_table_remove(msg->attr_table, attr); + } + + return; + } + + new_attr = g_strdup(attr); + + g_hash_table_insert(msg->attr_table, new_attr, g_strdup(value)); + + if (temp == NULL) + msg->attr_list = g_list_append(msg->attr_list, new_attr); +} + +const char * +msn_message_get_attr(const MsnMessage *msg, const char *attr) +{ + g_return_val_if_fail(msg != NULL, NULL); + g_return_val_if_fail(attr != NULL, NULL); + + return g_hash_table_lookup(msg->attr_table, attr); +} + +GHashTable * +msn_message_get_hashtable_from_body(const MsnMessage *msg) +{ + GHashTable *table; + size_t body_len; + const char *body; + char **elems, **cur, **tokens, *body_str; + + g_return_val_if_fail(msg != NULL, NULL); + + table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + + body = msn_message_get_bin_data(msg, &body_len); + + g_return_val_if_fail(body != NULL, NULL); + + body_str = g_strndup(body, body_len); + elems = g_strsplit(body_str, "\r\n", 0); + g_free(body_str); + + for (cur = elems; *cur != NULL; cur++) + { + if (**cur == '\0') + break; + + tokens = g_strsplit(*cur, ": ", 2); + + if (tokens[0] != NULL && tokens[1] != NULL) + g_hash_table_insert(table, tokens[0], tokens[1]); + + g_free(tokens); + } + + g_strfreev(elems); + + return table; +} + +char * +msn_message_to_string(MsnMessage *msg) +{ + size_t body_len; + const char *body; + + g_return_val_if_fail(msg != NULL, NULL); + g_return_val_if_fail(msg->type == MSN_MSG_TEXT, NULL); + + body = msn_message_get_bin_data(msg, &body_len); + + return g_strndup(body, body_len); +} + +void +msn_message_show_readable(MsnMessage *msg, const char *info, + gboolean text_body) +{ + GString *str; + size_t body_len; + const char *body; + GList *l; + + g_return_if_fail(msg != NULL); + + str = g_string_new(NULL); + + /* Standard header. */ + if (msg->charset == NULL) + { + g_string_append_printf(str, + "MIME-Version: 1.0\r\n" + "Content-Type: %s\r\n", + msg->content_type); + } + else + { + g_string_append_printf(str, + "MIME-Version: 1.0\r\n" + "Content-Type: %s; charset=%s\r\n", + msg->content_type, msg->charset); + } + + for (l = msg->attr_list; l; l = l->next) + { + char *key; + const char *value; + + key = l->data; + value = msn_message_get_attr(msg, key); + + g_string_append_printf(str, "%s: %s\r\n", key, value); + } + + g_string_append(str, "\r\n"); + + body = msn_message_get_bin_data(msg, &body_len); + + if (msg->msnslp_message) + { + g_string_append_printf(str, "Session ID: %u\r\n", msg->msnslp_header.session_id); + g_string_append_printf(str, "ID: %u\r\n", msg->msnslp_header.id); + g_string_append_printf(str, "Offset: %" G_GUINT64_FORMAT "\r\n", msg->msnslp_header.offset); + g_string_append_printf(str, "Total size: %" G_GUINT64_FORMAT "\r\n", msg->msnslp_header.total_size); + g_string_append_printf(str, "Length: %u\r\n", msg->msnslp_header.length); + g_string_append_printf(str, "Flags: 0x%x\r\n", msg->msnslp_header.flags); + g_string_append_printf(str, "ACK ID: %u\r\n", msg->msnslp_header.ack_id); + g_string_append_printf(str, "SUB ID: %u\r\n", msg->msnslp_header.ack_sub_id); + g_string_append_printf(str, "ACK Size: %" G_GUINT64_FORMAT "\r\n", msg->msnslp_header.ack_size); + +#ifdef MSN_DEBUG_SLP_VERBOSE + if (body != NULL) + { + if (text_body) + { + g_string_append_len(str, body, body_len); + if (body[body_len - 1] == '\0') + { + str->len--; + g_string_append(str, " 0x00"); + } + g_string_append(str, "\r\n"); + } + else + { + int i; + for (i = 0; i < msg->body_len; i++) + { + g_string_append_printf(str, "%.2hhX ", body[i]); + if ((i % 16) == 15) + g_string_append(str, "\r\n"); + } + g_string_append(str, "\r\n"); + } + } +#endif + + g_string_append_printf(str, "Footer: %u\r\n", msg->msnslp_footer.value); + } + else + { + if (body != NULL) + { + g_string_append_len(str, body, body_len); + g_string_append(str, "\r\n"); + } + } + + purple_debug_info("msn", "Message %s:\n{%s}\n", info, str->str); + + g_string_free(str, TRUE); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/msg.h Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,349 @@ +/** + * @file msg.h Message functions + * + * 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 _MSN_MSG_H_ +#define _MSN_MSG_H_ + +typedef struct _MsnMessage MsnMessage; + +#include "session.h" +#include "user.h" + +#include "command.h" +#include "transaction.h" + +typedef void (*MsnMsgCb)(MsnMessage *, void *data); + +/* +typedef enum +{ + MSN_MSG_NORMAL, + MSN_MSG_SLP_SB, + MSN_MSG_SLP_DC + +} MsnMsgType; +*/ + +typedef enum +{ + MSN_MSG_UNKNOWN, + MSN_MSG_TEXT, + MSN_MSG_TYPING, + MSN_MSG_CAPS, + MSN_MSG_SLP, + MSN_MSG_NUDGE + +} MsnMsgType; + +typedef enum +{ + MSN_MSG_ERROR_NONE, /**< No error. */ + MSN_MSG_ERROR_TIMEOUT, /**< The message timedout. */ + MSN_MSG_ERROR_NAK, /**< The message could not be sent. */ + MSN_MSG_ERROR_SB, /**< The error comes from the switchboard. */ + MSN_MSG_ERROR_UNKNOWN /**< An unknown error occurred. */ + +} MsnMsgErrorType; + +typedef struct +{ + guint32 session_id; + guint32 id; + guint64 offset; + guint64 total_size; + guint32 length; + guint32 flags; + guint32 ack_id; + guint32 ack_sub_id; + guint64 ack_size; + +} MsnSlpHeader; + +typedef struct +{ + guint32 value; + +} MsnSlpFooter; + +/** + * A message. + */ +struct _MsnMessage +{ + size_t ref_count; /**< The reference count. */ + + MsnMsgType type; + + gboolean msnslp_message; + + char *remote_user; + char flag; + + char *content_type; + char *charset; + char *body; + gsize body_len; + + MsnSlpHeader msnslp_header; + MsnSlpFooter msnslp_footer; + + GHashTable *attr_table; + GList *attr_list; + + gboolean ack_ref; /**< A flag that states if this message has + been ref'ed for using it in a callback. */ + + MsnCommand *cmd; + MsnTransaction *trans; + + MsnMsgCb ack_cb; /**< The callback to call when we receive an ACK of this + message. */ + MsnMsgCb nak_cb; /**< The callback to call when we receive a NAK of this + message. */ + void *ack_data; /**< The data used by callbacks. */ + + MsnMsgErrorType error; /**< The error of the message. */ +}; + +/** + * Creates a new, empty message. + * + * @return A new message. + */ +MsnMessage *msn_message_new(MsnMsgType type); + +/** + * Creates a new, empty MSNSLP message. + * + * @return A new MSNSLP message. + */ +MsnMessage *msn_message_new_msnslp(void); + +/** + * Creates a new nudge message. + * + * @return A new nudge message. + */ +MsnMessage *msn_message_new_nudge(void); + +/** + * Creates a new plain message. + * + * @return A new plain message. + */ +MsnMessage *msn_message_new_plain(const char *message); + +/** + * Creates a MSNSLP ack message. + * + * @param acked_msg The message to acknowledge. + * + * @return A new MSNSLP ack message. + */ +MsnMessage *msn_message_new_msnslp_ack(MsnMessage *acked_msg); + +/** + * Creates a new message based off a command. + * + * @param session The MSN session. + * @param cmd The command. + * + * @return The new message. + */ +MsnMessage *msn_message_new_from_cmd(MsnSession *session, MsnCommand *cmd); + +/** + * Parses the payload of a message. + * + * @param msg The message. + * @param payload The payload. + * @param payload_len The length of the payload. + */ +void msn_message_parse_payload(MsnMessage *msg, const char *payload, + size_t payload_len); + +/** + * Destroys a message. + * + * @param msg The message to destroy. + */ +void msn_message_destroy(MsnMessage *msg); + +/** + * Increments the reference count on a message. + * + * @param msg The message. + * + * @return @a msg + */ +MsnMessage *msn_message_ref(MsnMessage *msg); + +/** + * Decrements the reference count on a message. + * + * This will destroy the structure if the count hits 0. + * + * @param msg The message. + * + * @return @a msg, or @c NULL if the new count is 0. + */ +MsnMessage *msn_message_unref(MsnMessage *msg); + +/** + * Generates the payload data of a message. + * + * @param msg The message. + * @param ret_size The returned size of the payload. + * + * @return The payload data of the message. + */ +char *msn_message_gen_payload(MsnMessage *msg, size_t *ret_size); + +/** + * Sets the flag for an outgoing message. + * + * @param msg The message. + * @param flag The flag. + */ +void msn_message_set_flag(MsnMessage *msg, char flag); + +/** + * Returns the flag for an outgoing message. + * + * @param msg The message. + * + * @return The flag. + */ +char msn_message_get_flag(const MsnMessage *msg); + +#if 0 +/** + * Sets the body of a message. + * + * @param msg The message. + * @param body The body of the message. + */ +void msn_message_set_body(MsnMessage *msg, const char *body); + +/** + * Returns the body of the message. + * + * @param msg The message. + * + * @return The body of the message. + */ +const char *msn_message_get_body(const MsnMessage *msg); +#endif +/** + * Sets the binary content of the message. + * + * @param msg The message. + * @param data The binary data. + * @param len The length of the data. + */ +void msn_message_set_bin_data(MsnMessage *msg, const void *data, size_t len); + +/** + * Returns the binary content of the message. + * + * @param msg The message. + * @param len The returned length of the data. + * + * @return The binary data. + */ +const void *msn_message_get_bin_data(const MsnMessage *msg, size_t *len); + +/** + * Sets the content type in a message. + * + * @param msg The message. + * @param type The content-type. + */ +void msn_message_set_content_type(MsnMessage *msg, const char *type); + +/** + * Returns the content type in a message. + * + * @param msg The message. + * + * @return The content-type. + */ +const char *msn_message_get_content_type(const MsnMessage *msg); + +/** + * Sets the charset in a message. + * + * @param msg The message. + * @param charset The charset. + */ +void msn_message_set_charset(MsnMessage *msg, const char *charset); + +/** + * Returns the charset in a message. + * + * @param msg The message. + * + * @return The charset. + */ +const char *msn_message_get_charset(const MsnMessage *msg); + +/** + * Sets an attribute in a message. + * + * @param msg The message. + * @param attr The attribute name. + * @param value The attribute value. + */ +void msn_message_set_attr(MsnMessage *msg, const char *attr, + const char *value); + +/** + * Returns an attribute from a message. + * + * @param msg The message. + * @param attr The attribute. + * + * @return The value, or @c NULL if not found. + */ +const char *msn_message_get_attr(const MsnMessage *msg, const char *attr); + +/** + * Parses the body and returns it in the form of a hashtable. + * + * @param msg The message. + * + * @return The resulting hashtable. + */ +GHashTable *msn_message_get_hashtable_from_body(const MsnMessage *msg); + +void msn_message_show_readable(MsnMessage *msg, const char *info, + gboolean text_body); + +void msn_message_parse_slp_body(MsnMessage *msg, const char *body, + size_t len); + +char *msn_message_gen_slp_body(MsnMessage *msg, size_t *ret_size); + +char *msn_message_to_string(MsnMessage *msg); + +#endif /* _MSN_MSG_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/msn-utils.c Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,440 @@ +/** + * @file msn-utils.c Utility functions + * + * 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 "msn.h" +#include "msn-utils.h" + +void +msn_parse_format(const char *mime, char **pre_ret, char **post_ret) +{ + char *cur; + GString *pre = g_string_new(NULL); + GString *post = g_string_new(NULL); + unsigned int colors[3]; + + if (pre_ret != NULL) *pre_ret = NULL; + if (post_ret != NULL) *post_ret = NULL; + + cur = strstr(mime, "FN="); + + if (cur && (*(cur = cur + 3) != ';')) + { + pre = g_string_append(pre, "<FONT FACE=\""); + + while (*cur && *cur != ';') + { + pre = g_string_append_c(pre, *cur); + cur++; + } + + pre = g_string_append(pre, "\">"); + post = g_string_prepend(post, "</FONT>"); + } + + cur = strstr(mime, "EF="); + + if (cur && (*(cur = cur + 3) != ';')) + { + while (*cur && *cur != ';') + { + pre = g_string_append_c(pre, '<'); + pre = g_string_append_c(pre, *cur); + pre = g_string_append_c(pre, '>'); + post = g_string_prepend_c(post, '>'); + post = g_string_prepend_c(post, *cur); + post = g_string_prepend_c(post, '/'); + post = g_string_prepend_c(post, '<'); + cur++; + } + } + + cur = strstr(mime, "CO="); + + if (cur && (*(cur = cur + 3) != ';')) + { + int i; + + i = sscanf(cur, "%02x%02x%02x;", &colors[0], &colors[1], &colors[2]); + + if (i > 0) + { + char tag[64]; + + if (i == 1) + { + colors[1] = 0; + colors[2] = 0; + } + else if (i == 2) + { + unsigned int temp = colors[0]; + + colors[0] = colors[1]; + colors[1] = temp; + colors[2] = 0; + } + else if (i == 3) + { + unsigned int temp = colors[2]; + + colors[2] = colors[0]; + colors[0] = temp; + } + + g_snprintf(tag, sizeof(tag), + "<FONT COLOR=\"#%02hhx%02hhx%02hhx\">", + colors[0], colors[1], colors[2]); + + pre = g_string_append(pre, tag); + post = g_string_prepend(post, "</FONT>"); + } + } + + cur = strstr(mime, "RL="); + + if (cur && (*(cur = cur + 3) != ';')) + { + if (*cur == '1') + { + /* RTL text was received */ + pre = g_string_append(pre, "<SPAN style=\"direction:rtl;text-align:right;\">"); + post = g_string_prepend(post, "</SPAN>"); + } + } + + cur = g_strdup(purple_url_decode(pre->str)); + g_string_free(pre, TRUE); + + if (pre_ret != NULL) + *pre_ret = cur; + else + g_free(cur); + + cur = g_strdup(purple_url_decode(post->str)); + g_string_free(post, TRUE); + + if (post_ret != NULL) + *post_ret = cur; + else + g_free(cur); +} + +/* + * We need this because we're only supposed to encode spaces in the font + * names. purple_url_encode() isn't acceptable. + */ +static const char * +encode_spaces(const char *str) +{ + static char buf[BUF_LEN]; + const char *c; + char *d; + + g_return_val_if_fail(str != NULL, NULL); + + for (c = str, d = buf; *c != '\0'; c++) + { + if (*c == ' ') + { + *d++ = '%'; + *d++ = '2'; + *d++ = '0'; + } + else + *d++ = *c; + } + + return buf; +} + +/* + * Taken from the zephyr plugin. + * This parses HTML formatting (put out by one of the gtkimhtml widgets + * and converts it to msn formatting. It doesn't deal with the tag closing, + * but gtkimhtml widgets give valid html. + * It currently deals properly with <b>, <u>, <i>, <font face=...>, + * <font color=...>, <span dir=...>, <span style="direction: ...">. + * It ignores <font back=...> and <font size=...> + */ +void +msn_import_html(const char *html, char **attributes, char **message) +{ + int len, retcount = 0; + const char *c; + char *msg; + char *fontface = NULL; + char fonteffect[4]; + char fontcolor[7]; + char direction = '0'; + + gboolean has_bold = FALSE; + gboolean has_italic = FALSE; + gboolean has_underline = FALSE; + gboolean has_strikethrough = FALSE; + + g_return_if_fail(html != NULL); + g_return_if_fail(attributes != NULL); + g_return_if_fail(message != NULL); + + len = strlen(html); + msg = g_malloc0(len + 1); + + memset(fontcolor, 0, sizeof(fontcolor)); + strcat(fontcolor, "0"); + memset(fonteffect, 0, sizeof(fonteffect)); + + for (c = html; *c != '\0';) + { + if (*c == '<') + { + if (!g_ascii_strncasecmp(c + 1, "br>", 3)) + { + msg[retcount++] = '\r'; + msg[retcount++] = '\n'; + c += 4; + } + else if (!g_ascii_strncasecmp(c + 1, "i>", 2)) + { + if (!has_italic) + { + strcat(fonteffect, "I"); + has_italic = TRUE; + } + c += 3; + } + else if (!g_ascii_strncasecmp(c + 1, "b>", 2)) + { + if (!has_bold) + { + strcat(fonteffect, "B"); + has_bold = TRUE; + } + c += 3; + } + else if (!g_ascii_strncasecmp(c + 1, "u>", 2)) + { + if (!has_underline) + { + strcat(fonteffect, "U"); + has_underline = TRUE; + } + c += 3; + } + else if (!g_ascii_strncasecmp(c + 1, "s>", 2)) + { + if (!has_strikethrough) + { + strcat(fonteffect, "S"); + has_strikethrough = TRUE; + } + c += 3; + } + else if (!g_ascii_strncasecmp(c + 1, "a href=\"", 8)) + { + c += 9; + + if (!g_ascii_strncasecmp(c, "mailto:", 7)) + c += 7; + + while ((*c != '\0') && g_ascii_strncasecmp(c, "\">", 2)) + msg[retcount++] = *c++; + + if (*c != '\0') + c += 2; + + /* ignore descriptive string */ + while ((*c != '\0') && g_ascii_strncasecmp(c, "</a>", 4)) + c++; + + if (*c != '\0') + c += 4; + } + else if (!g_ascii_strncasecmp(c + 1, "span", 4)) + { + /* Bi-directional text support using CSS properties in span tags */ + c += 5; + + while (*c != '\0' && *c != '>') + { + while (*c == ' ') + c++; + if (!g_ascii_strncasecmp(c, "dir=\"rtl\"", 9)) + { + c += 9; + direction = '1'; + } + else if (!g_ascii_strncasecmp(c, "style=\"", 7)) + { + /* Parse inline CSS attributes */ + char *attributes; + int attr_len = 0; + c += 7; + while (*(c + attr_len) != '\0' && *(c + attr_len) != '"') + attr_len++; + if (*(c + attr_len) == '"') + { + char *attr_dir; + attributes = g_strndup(c, attr_len); + attr_dir = purple_markup_get_css_property(attributes, "direction"); + if (attr_dir && (!g_ascii_strncasecmp(attr_dir, "RTL", 3))) + direction = '1'; + g_free(attr_dir); + g_free(attributes); + } + + } + else + { + c++; + } + } + if (*c == '>') + c++; + } + else if (!g_ascii_strncasecmp(c + 1, "font", 4)) + { + c += 5; + + while ((*c != '\0') && !g_ascii_strncasecmp(c, " ", 1)) + c++; + + if (!g_ascii_strncasecmp(c, "color=\"#", 7)) + { + c += 8; + + fontcolor[0] = *(c + 4); + fontcolor[1] = *(c + 5); + fontcolor[2] = *(c + 2); + fontcolor[3] = *(c + 3); + fontcolor[4] = *c; + fontcolor[5] = *(c + 1); + + c += 8; + } + else if (!g_ascii_strncasecmp(c, "face=\"", 6)) + { + const char *end = NULL; + const char *comma = NULL; + unsigned int namelen = 0; + + c += 6; + end = strchr(c, '\"'); + comma = strchr(c, ','); + + if (comma == NULL || comma > end) + namelen = (unsigned int)(end - c); + else + namelen = (unsigned int)(comma - c); + + fontface = g_strndup(c, namelen); + c = end + 2; + } + else + { + /* Drop all unrecognized/misparsed font tags */ + while ((*c != '\0') && g_ascii_strncasecmp(c, "\">", 2)) + c++; + + if (*c != '\0') + c += 2; + } + } + else + { + while ((*c != '\0') && (*c != '>')) + c++; + if (*c != '\0') + c++; + } + } + else if (*c == '&') + { + if (!g_ascii_strncasecmp(c, "<", 4)) + { + msg[retcount++] = '<'; + c += 4; + } + else if (!g_ascii_strncasecmp(c, ">", 4)) + { + msg[retcount++] = '>'; + c += 4; + } + else if (!g_ascii_strncasecmp(c, " ", 6)) + { + msg[retcount++] = ' '; + c += 6; + } + else if (!g_ascii_strncasecmp(c, """, 6)) + { + msg[retcount++] = '"'; + c += 6; + } + else if (!g_ascii_strncasecmp(c, "&", 5)) + { + msg[retcount++] = '&'; + c += 5; + } + else if (!g_ascii_strncasecmp(c, "'", 6)) + { + msg[retcount++] = '\''; + c += 6; + } + else + msg[retcount++] = *c++; + } + else + msg[retcount++] = *c++; + } + + if (fontface == NULL) + fontface = g_strdup("MS Sans Serif"); + + *attributes = g_strdup_printf("FN=%s; EF=%s; CO=%s; PF=0; RL=%c", + encode_spaces(fontface), + fonteffect, fontcolor, direction); + *message = g_strdup(msg); + + g_free(fontface); + g_free(msg); +} + +void +msn_parse_socket(const char *str, char **ret_host, int *ret_port) +{ + char *host; + char *c; + int port; + + host = g_strdup(str); + + if ((c = strchr(host, ':')) != NULL) + { + *c = '\0'; + port = atoi(c + 1); + } + else + port = 1863; + + *ret_host = host; + *ret_port = port; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/msn-utils.h Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,51 @@ +/** + * @file msn-utils.h Utility functions + * + * 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 _MSN_UTILS_H_ +#define _MSN_UTILS_H_ + +/** + * Parses the MSN message formatting into a format compatible with Purple. + * + * @param mime The mime header with the formatting. + * @param pre_ret The returned prefix string. + * @param post_ret The returned postfix string. + * + * @return The new message. + */ +void msn_parse_format(const char *mime, char **pre_ret, char **post_ret); + +/** + * Parses the Purple message formatting (html) into the MSN format. + * + * @param html The html message to format. + * @param attributes The returned attributes string. + * @param message The returned message string. + * + * @return The new message. + */ +void msn_import_html(const char *html, char **attributes, char **message); + +void msn_parse_socket(const char *str, char **ret_host, int *ret_port); + +#endif /* _MSN_UTILS_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/msn.c Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,2221 @@ +/** + * @file msn.c The MSN protocol plugin + * + * 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 + */ +#define PHOTO_SUPPORT 1 + +#include <glib.h> + +#include "msn.h" +#include "accountopt.h" +#include "msg.h" +#include "page.h" +#include "pluginpref.h" +#include "prefs.h" +#include "session.h" +#include "state.h" +#include "util.h" +#include "cmds.h" +#include "core.h" +#include "prpl.h" +#include "msn-utils.h" +#include "version.h" + +#include "switchboard.h" +#include "notification.h" +#include "sync.h" +#include "slplink.h" + +#if PHOTO_SUPPORT +#include "imgstore.h" +#endif + +typedef struct +{ + PurpleConnection *gc; + const char *passport; + +} MsnMobileData; + +typedef struct +{ + PurpleConnection *gc; + char *name; + +} MsnGetInfoData; + +typedef struct +{ + MsnGetInfoData *info_data; + char *stripped; + char *url_buffer; + PurpleNotifyUserInfo *user_info; + char *photo_url_text; + +} MsnGetInfoStepTwoData; + +typedef struct +{ + PurpleConnection *gc; + const char *who; + char *msg; + PurpleMessageFlags flags; + time_t when; +} MsnIMData; + +static const char * +msn_normalize(const PurpleAccount *account, const char *str) +{ + static char buf[BUF_LEN]; + char *tmp; + + g_return_val_if_fail(str != NULL, NULL); + + g_snprintf(buf, sizeof(buf), "%s%s", str, + (strchr(str, '@') ? "" : "@hotmail.com")); + + tmp = g_utf8_strdown(buf, -1); + strncpy(buf, tmp, sizeof(buf)); + g_free(tmp); + + return buf; +} + +static gboolean +msn_send_attention(PurpleConnection *gc, const char *username, guint type) +{ + MsnMessage *msg; + MsnSession *session; + MsnSwitchBoard *swboard; + + msg = msn_message_new_nudge(); + session = gc->proto_data; + swboard = msn_session_get_swboard(session, username, MSN_SB_FLAG_IM); + + if (swboard == NULL) + return FALSE; + + msn_switchboard_send_msg(swboard, msg, TRUE); + + 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; +} + +static void +msn_act_id(PurpleConnection *gc, const char *entry) +{ + MsnCmdProc *cmdproc; + MsnSession *session; + PurpleAccount *account; + const char *alias; + + session = gc->proto_data; + cmdproc = session->notification->cmdproc; + account = purple_connection_get_account(gc); + + if(entry && strlen(entry)) + alias = purple_url_encode(entry); + else + alias = ""; + + if (strlen(alias) > BUDDY_ALIAS_MAXLEN) + { + purple_notify_error(gc, NULL, + _("Your new MSN friendly name is too long."), NULL); + return; + } + + msn_cmdproc_send(cmdproc, "REA", "%s %s", + purple_account_get_username(account), + alias); +} + +static void +msn_set_prp(PurpleConnection *gc, const char *type, const char *entry) +{ + MsnCmdProc *cmdproc; + MsnSession *session; + + session = gc->proto_data; + cmdproc = session->notification->cmdproc; + + if (entry == NULL || *entry == '\0') + { + msn_cmdproc_send(cmdproc, "PRP", "%s", type); + } + else + { + msn_cmdproc_send(cmdproc, "PRP", "%s %s", type, + purple_url_encode(entry)); + } +} + +static void +msn_set_home_phone_cb(PurpleConnection *gc, const char *entry) +{ + msn_set_prp(gc, "PHH", entry); +} + +static void +msn_set_work_phone_cb(PurpleConnection *gc, const char *entry) +{ + msn_set_prp(gc, "PHW", entry); +} + +static void +msn_set_mobile_phone_cb(PurpleConnection *gc, const char *entry) +{ + msn_set_prp(gc, "PHM", entry); +} + +static void +enable_msn_pages_cb(PurpleConnection *gc) +{ + msn_set_prp(gc, "MOB", "Y"); +} + +static void +disable_msn_pages_cb(PurpleConnection *gc) +{ + msn_set_prp(gc, "MOB", "N"); +} + +static void +send_to_mobile(PurpleConnection *gc, const char *who, const char *entry) +{ + MsnTransaction *trans; + MsnSession *session; + MsnCmdProc *cmdproc; + MsnPage *page; + char *payload; + size_t payload_len; + + session = gc->proto_data; + cmdproc = session->notification->cmdproc; + + page = msn_page_new(); + msn_page_set_body(page, entry); + + payload = msn_page_gen_payload(page, &payload_len); + + trans = msn_transaction_new(cmdproc, "PGD", "%s 1 %d", who, payload_len); + + msn_transaction_set_payload(trans, payload, payload_len); + + msn_page_destroy(page); + + msn_cmdproc_send_trans(cmdproc, trans); +} + +static void +send_to_mobile_cb(MsnMobileData *data, const char *entry) +{ + send_to_mobile(data->gc, data->passport, entry); + g_free(data); +} + +static void +close_mobile_page_cb(MsnMobileData *data, const char *entry) +{ + g_free(data); +} + +/* -- */ + +static void +msn_show_set_friendly_name(PurplePluginAction *action) +{ + PurpleConnection *gc; + + gc = (PurpleConnection *) action->context; + + purple_request_input(gc, NULL, _("Set your friendly name."), + _("This is the name that other MSN buddies will " + "see you as."), + purple_connection_get_display_name(gc), FALSE, FALSE, NULL, + _("OK"), G_CALLBACK(msn_act_id), + _("Cancel"), NULL, + purple_connection_get_account(gc), NULL, NULL, + gc); +} + +static void +msn_show_set_home_phone(PurplePluginAction *action) +{ + PurpleConnection *gc; + MsnSession *session; + + gc = (PurpleConnection *) action->context; + session = gc->proto_data; + + purple_request_input(gc, NULL, _("Set your home phone number."), NULL, + msn_user_get_home_phone(session->user), FALSE, FALSE, NULL, + _("OK"), G_CALLBACK(msn_set_home_phone_cb), + _("Cancel"), NULL, + purple_connection_get_account(gc), NULL, NULL, + gc); +} + +static void +msn_show_set_work_phone(PurplePluginAction *action) +{ + PurpleConnection *gc; + MsnSession *session; + + gc = (PurpleConnection *) action->context; + session = gc->proto_data; + + purple_request_input(gc, NULL, _("Set your work phone number."), NULL, + msn_user_get_work_phone(session->user), FALSE, FALSE, NULL, + _("OK"), G_CALLBACK(msn_set_work_phone_cb), + _("Cancel"), NULL, + purple_connection_get_account(gc), NULL, NULL, + gc); +} + +static void +msn_show_set_mobile_phone(PurplePluginAction *action) +{ + PurpleConnection *gc; + MsnSession *session; + + gc = (PurpleConnection *) action->context; + session = gc->proto_data; + + purple_request_input(gc, NULL, _("Set your mobile phone number."), NULL, + msn_user_get_mobile_phone(session->user), FALSE, FALSE, NULL, + _("OK"), G_CALLBACK(msn_set_mobile_phone_cb), + _("Cancel"), NULL, + purple_connection_get_account(gc), NULL, NULL, + gc); +} + +static void +msn_show_set_mobile_pages(PurplePluginAction *action) +{ + PurpleConnection *gc; + + gc = (PurpleConnection *) action->context; + + purple_request_action(gc, NULL, _("Allow MSN Mobile pages?"), + _("Do you want to allow or disallow people on " + "your buddy list to send you MSN Mobile pages " + "to your cell phone or other mobile device?"), + -1, + purple_connection_get_account(gc), NULL, NULL, + gc, 3, + _("Allow"), G_CALLBACK(enable_msn_pages_cb), + _("Disallow"), G_CALLBACK(disable_msn_pages_cb), + _("Cancel"), NULL); +} + +static void +msn_show_hotmail_inbox(PurplePluginAction *action) +{ + PurpleConnection *gc; + MsnSession *session; + + gc = (PurpleConnection *) action->context; + session = gc->proto_data; + + if (session->passport_info.file == NULL) + { + purple_notify_error(gc, NULL, + _("This Hotmail account may not be active."), NULL); + return; + } + + purple_notify_uri(gc, session->passport_info.file); +} + +static void +show_send_to_mobile_cb(PurpleBlistNode *node, gpointer ignored) +{ + PurpleBuddy *buddy; + PurpleConnection *gc; + MsnSession *session; + MsnMobileData *data; + + g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node)); + + buddy = (PurpleBuddy *) node; + gc = purple_account_get_connection(buddy->account); + + session = gc->proto_data; + + data = g_new0(MsnMobileData, 1); + data->gc = gc; + data->passport = buddy->name; + + purple_request_input(gc, NULL, _("Send a mobile message."), NULL, + NULL, TRUE, FALSE, NULL, + _("Page"), G_CALLBACK(send_to_mobile_cb), + _("Close"), G_CALLBACK(close_mobile_page_cb), + purple_connection_get_account(gc), purple_buddy_get_name(buddy), NULL, + data); +} + +static gboolean +msn_offline_message(const PurpleBuddy *buddy) { + MsnUser *user; + if (buddy == NULL) + return FALSE; + user = buddy->proto_data; + return user && user->mobile; +} + +static void +initiate_chat_cb(PurpleBlistNode *node, gpointer data) +{ + PurpleBuddy *buddy; + PurpleConnection *gc; + + MsnSession *session; + MsnSwitchBoard *swboard; + + g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node)); + + buddy = (PurpleBuddy *) node; + gc = purple_account_get_connection(buddy->account); + + session = gc->proto_data; + + swboard = msn_switchboard_new(session); + msn_switchboard_request(swboard); + msn_switchboard_request_add_user(swboard, buddy->name); + + /* TODO: This might move somewhere else, after USR might be */ + swboard->chat_id = session->conv_seq++; + swboard->conv = serv_got_joined_chat(gc, swboard->chat_id, "MSN Chat"); + swboard->flag = MSN_SB_FLAG_IM; + + purple_conv_chat_add_user(PURPLE_CONV_CHAT(swboard->conv), + purple_account_get_username(buddy->account), NULL, PURPLE_CBFLAGS_NONE, TRUE); +} + +static void +t_msn_xfer_init(PurpleXfer *xfer) +{ + MsnSlpLink *slplink = xfer->data; + msn_slplink_request_ft(slplink, xfer); +} + +static PurpleXfer* +msn_new_xfer(PurpleConnection *gc, const char *who) +{ + MsnSession *session; + MsnSlpLink *slplink; + PurpleXfer *xfer; + + session = gc->proto_data; + + xfer = purple_xfer_new(gc->account, PURPLE_XFER_SEND, who); + if (xfer) + { + slplink = msn_session_get_slplink(session, who); + + xfer->data = slplink; + + purple_xfer_set_init_fnc(xfer, t_msn_xfer_init); + } + + return xfer; +} + +static void +msn_send_file(PurpleConnection *gc, const char *who, const char *file) +{ + PurpleXfer *xfer = msn_new_xfer(gc, who); + + if (file) + purple_xfer_request_accepted(xfer, file); + else + purple_xfer_request(xfer); +} + +static gboolean +msn_can_receive_file(PurpleConnection *gc, const char *who) +{ + PurpleAccount *account; + char *normal; + gboolean ret; + + account = purple_connection_get_account(gc); + + normal = g_strdup(msn_normalize(account, purple_account_get_username(account))); + + ret = strcmp(normal, msn_normalize(account, who)); + + g_free(normal); + + return ret; +} + +/************************************************************************** + * Protocol Plugin ops + **************************************************************************/ + +static const char * +msn_list_icon(PurpleAccount *a, PurpleBuddy *b) +{ + return "msn"; +} + +static char * +msn_status_text(PurpleBuddy *buddy) +{ + PurplePresence *presence; + PurpleStatus *status; + + presence = purple_buddy_get_presence(buddy); + status = purple_presence_get_active_status(presence); + + if (!purple_presence_is_available(presence) && !purple_presence_is_idle(presence)) + { + return g_strdup(purple_status_get_name(status)); + } + + return NULL; +} + +static void +msn_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full) +{ + MsnUser *user; + PurplePresence *presence = purple_buddy_get_presence(buddy); + PurpleStatus *status = purple_presence_get_active_status(presence); + + user = buddy->proto_data; + + + if (purple_presence_is_online(presence)) + { + purple_notify_user_info_add_pair(user_info, _("Status"), + (purple_presence_is_idle(presence) ? _("Idle") : purple_status_get_name(status))); + } + + if (full && user) + { + purple_notify_user_info_add_pair(user_info, _("Has you"), + ((user->list_op & (1 << MSN_LIST_RL)) ? _("Yes") : _("No"))); + } + + /* XXX: This is being shown in non-full tooltips because the + * XXX: blocked icon overlay isn't always accurate for MSN. + * XXX: This can die as soon as purple_privacy_check() knows that + * XXX: this prpl always honors both the allow and deny lists. */ + /* While the above comment may be strictly correct (the privacy API needs + * rewriteing), purple_privacy_check() is going to be more accurate at + * indicating whether a particular buddy is going to be able to message + * you, which is the important information that this is trying to convey. */ + if (full && user) + { + purple_notify_user_info_add_pair(user_info, _("Blocked"), + ((user->list_op & (1 << MSN_LIST_BL)) ? _("Yes") : _("No"))); + } +} + +static GList * +msn_status_types(PurpleAccount *account) +{ + PurpleStatusType *status; + GList *types = NULL; + + status = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE, + NULL, NULL, FALSE, TRUE, FALSE); + types = g_list_append(types, status); + + status = purple_status_type_new_full(PURPLE_STATUS_AWAY, + NULL, NULL, FALSE, TRUE, FALSE); + types = g_list_append(types, status); + + status = purple_status_type_new_full(PURPLE_STATUS_AWAY, + "brb", _("Be Right Back"), FALSE, TRUE, FALSE); + types = g_list_append(types, status); + + status = purple_status_type_new_full(PURPLE_STATUS_UNAVAILABLE, + "busy", _("Busy"), FALSE, TRUE, FALSE); + types = g_list_append(types, status); + + status = purple_status_type_new_full(PURPLE_STATUS_UNAVAILABLE, + "phone", _("On the Phone"), FALSE, TRUE, FALSE); + types = g_list_append(types, status); + + status = purple_status_type_new_full(PURPLE_STATUS_AWAY, + "lunch", _("Out to Lunch"), FALSE, TRUE, FALSE); + types = g_list_append(types, status); + + status = purple_status_type_new_full(PURPLE_STATUS_INVISIBLE, + NULL, NULL, FALSE, TRUE, FALSE); + types = g_list_append(types, status); + + status = purple_status_type_new_full(PURPLE_STATUS_OFFLINE, + NULL, NULL, FALSE, TRUE, FALSE); + types = g_list_append(types, status); + + status = purple_status_type_new_full(PURPLE_STATUS_MOBILE, + "mobile", NULL, FALSE, FALSE, TRUE); + types = g_list_append(types, status); + + return types; +} + +static GList * +msn_actions(PurplePlugin *plugin, gpointer context) +{ + PurpleConnection *gc = (PurpleConnection *)context; + PurpleAccount *account; + const char *user; + + GList *m = NULL; + PurplePluginAction *act; + + act = purple_plugin_action_new(_("Set Friendly Name..."), + msn_show_set_friendly_name); + m = g_list_append(m, act); + m = g_list_append(m, NULL); + + act = purple_plugin_action_new(_("Set Home Phone Number..."), + msn_show_set_home_phone); + m = g_list_append(m, act); + + act = purple_plugin_action_new(_("Set Work Phone Number..."), + msn_show_set_work_phone); + m = g_list_append(m, act); + + act = purple_plugin_action_new(_("Set Mobile Phone Number..."), + msn_show_set_mobile_phone); + m = g_list_append(m, act); + m = g_list_append(m, NULL); + +#if 0 + act = purple_plugin_action_new(_("Enable/Disable Mobile Devices..."), + msn_show_set_mobile_support); + m = g_list_append(m, act); +#endif + + act = purple_plugin_action_new(_("Allow/Disallow Mobile Pages..."), + msn_show_set_mobile_pages); + m = g_list_append(m, act); + + account = purple_connection_get_account(gc); + user = msn_normalize(account, purple_account_get_username(account)); + + if ((strstr(user, "@hotmail.") != NULL) || + (strstr(user, "@msn.com") != NULL)) + { + m = g_list_append(m, NULL); + act = purple_plugin_action_new(_("Open Hotmail Inbox"), + msn_show_hotmail_inbox); + m = g_list_append(m, act); + } + + return m; +} + +static GList * +msn_buddy_menu(PurpleBuddy *buddy) +{ + MsnUser *user; + + GList *m = NULL; + PurpleMenuAction *act; + + g_return_val_if_fail(buddy != NULL, NULL); + + user = buddy->proto_data; + + if (user != NULL) + { + if (user->mobile) + { + act = purple_menu_action_new(_("Send to Mobile"), + PURPLE_CALLBACK(show_send_to_mobile_cb), + NULL, NULL); + m = g_list_append(m, act); + } + } + + if (g_ascii_strcasecmp(buddy->name, + purple_account_get_username(buddy->account))) + { + act = purple_menu_action_new(_("Initiate _Chat"), + PURPLE_CALLBACK(initiate_chat_cb), + NULL, NULL); + m = g_list_append(m, act); + } + + return m; +} + +static GList * +msn_blist_node_menu(PurpleBlistNode *node) +{ + if(PURPLE_BLIST_NODE_IS_BUDDY(node)) + { + return msn_buddy_menu((PurpleBuddy *) node); + } + else + { + return NULL; + } +} + +static void +msn_login(PurpleAccount *account) +{ + PurpleConnection *gc; + MsnSession *session; + const char *username; + const char *host; + gboolean http_method = FALSE; + int port; + + gc = purple_account_get_connection(account); + + if (!purple_ssl_is_supported()) + { + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, + + _("SSL support is needed for MSN. Please install a supported " + "SSL library.")); + return; + } + + http_method = purple_account_get_bool(account, "http_method", FALSE); + + if (http_method) + host = purple_account_get_string(account, "http_method_server", MSN_HTTPCONN_SERVER); + else + host = purple_account_get_string(account, "server", MSN_SERVER); + port = purple_account_get_int(account, "port", MSN_PORT); + + session = msn_session_new(account); + + gc->proto_data = session; + gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR | PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC; + + msn_session_set_login_step(session, MSN_LOGIN_STEP_START); + + /* Hmm, I don't like this. */ + /* XXX shx: Me neither */ + username = msn_normalize(account, purple_account_get_username(account)); + + if (strcmp(username, purple_account_get_username(account))) + purple_account_set_username(account, username); + + if (!msn_session_connect(session, host, port, http_method)) + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Failed to connect to server.")); +} + +static void +msn_close(PurpleConnection *gc) +{ + MsnSession *session; + + session = gc->proto_data; + + g_return_if_fail(session != NULL); + + msn_session_destroy(session); + + gc->proto_data = NULL; +} + +static gboolean +msn_send_me_im(gpointer data) +{ + MsnIMData *imdata = data; + serv_got_im(imdata->gc, imdata->who, imdata->msg, imdata->flags, imdata->when); + g_free(imdata->msg); + g_free(imdata); + return FALSE; +} + +static int +msn_send_im(PurpleConnection *gc, const char *who, const char *message, + PurpleMessageFlags flags) +{ + PurpleAccount *account; + PurpleBuddy *buddy = purple_find_buddy(gc->account, who); + MsnMessage *msg; + char *msgformat; + char *msgtext; + + account = purple_connection_get_account(gc); + + if (buddy) { + PurplePresence *p = purple_buddy_get_presence(buddy); + if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_MOBILE)) { + char *text = purple_markup_strip_html(message); + send_to_mobile(gc, who, text); + g_free(text); + return 1; + } + } + + msn_import_html(message, &msgformat, &msgtext); + + if (strlen(msgtext) + strlen(msgformat) + strlen(DISPLAY_VERSION) > 1564) + { + g_free(msgformat); + g_free(msgtext); + + return -E2BIG; + } + + msg = msn_message_new_plain(msgtext); + msn_message_set_attr(msg, "X-MMS-IM-Format", msgformat); + + g_free(msgformat); + g_free(msgtext); + + if (g_ascii_strcasecmp(who, purple_account_get_username(account))) + { + MsnSession *session; + MsnSwitchBoard *swboard; + + session = gc->proto_data; + swboard = msn_session_get_swboard(session, who, MSN_SB_FLAG_IM); + + msn_switchboard_send_msg(swboard, msg, TRUE); + } + else + { + char *body_str, *body_enc, *pre, *post; + const char *format; + MsnIMData *imdata = g_new0(MsnIMData, 1); + /* + * In MSN, you can't send messages to yourself, so + * we'll fake like we received it ;) + */ + body_str = msn_message_to_string(msg); + body_enc = g_markup_escape_text(body_str, -1); + g_free(body_str); + + format = msn_message_get_attr(msg, "X-MMS-IM-Format"); + msn_parse_format(format, &pre, &post); + body_str = g_strdup_printf("%s%s%s", pre ? pre : "", + body_enc ? body_enc : "", post ? post : ""); + g_free(body_enc); + g_free(pre); + g_free(post); + + serv_got_typing_stopped(gc, who); + imdata->gc = gc; + imdata->who = who; + imdata->msg = body_str; + imdata->flags = flags; + imdata->when = time(NULL); + g_idle_add(msn_send_me_im, imdata); + } + + msn_message_destroy(msg); + + return 1; +} + +static unsigned int +msn_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state) +{ + PurpleAccount *account; + MsnSession *session; + MsnSwitchBoard *swboard; + MsnMessage *msg; + + account = purple_connection_get_account(gc); + session = gc->proto_data; + + /* + * TODO: I feel like this should be "if (state != PURPLE_TYPING)" + * but this is how it was before, and I don't want to break + * anything. --KingAnt + */ + if (state == PURPLE_NOT_TYPING) + return 0; + + if (!g_ascii_strcasecmp(who, purple_account_get_username(account))) + { + /* We'll just fake it, since we're sending to ourself. */ + serv_got_typing(gc, who, MSN_TYPING_RECV_TIMEOUT, PURPLE_TYPING); + + return MSN_TYPING_SEND_TIMEOUT; + } + + swboard = msn_session_find_swboard(session, who); + + if (swboard == NULL || !msn_switchboard_can_send(swboard)) + return 0; + + swboard->flag |= MSN_SB_FLAG_IM; + + msg = msn_message_new(MSN_MSG_TYPING); + msn_message_set_content_type(msg, "text/x-msmsgscontrol"); + msn_message_set_flag(msg, 'U'); + msn_message_set_attr(msg, "TypingUser", + purple_account_get_username(account)); + msn_message_set_bin_data(msg, "\r\n", 2); + + msn_switchboard_send_msg(swboard, msg, FALSE); + + msn_message_destroy(msg); + + return MSN_TYPING_SEND_TIMEOUT; +} + +static void +msn_set_status(PurpleAccount *account, PurpleStatus *status) +{ + PurpleConnection *gc; + MsnSession *session; + + gc = purple_account_get_connection(account); + + if (gc != NULL) + { + session = gc->proto_data; + msn_change_status(session); + } +} + +static void +msn_set_idle(PurpleConnection *gc, int idle) +{ + MsnSession *session; + + session = gc->proto_data; + + msn_change_status(session); +} + +#if 0 +static void +fake_userlist_add_buddy(MsnUserList *userlist, + const char *who, int list_id, + const char *group_name) +{ + MsnUser *user; + static int group_id_c = 1; + int group_id; + + group_id = -1; + + if (group_name != NULL) + { + MsnGroup *group; + group = msn_group_new(userlist, group_id_c, group_name); + group_id = group_id_c++; + } + + user = msn_userlist_find_user(userlist, who); + + if (user == NULL) + { + user = msn_user_new(userlist, who, NULL); + msn_userlist_add_user(userlist, user); + } + else + if (user->list_op & (1 << list_id)) + { + if (list_id == MSN_LIST_FL) + { + if (group_id >= 0) + if (g_list_find(user->group_ids, + GINT_TO_POINTER(group_id))) + return; + } + else + return; + } + + if (group_id >= 0) + { + user->group_ids = g_list_append(user->group_ids, + GINT_TO_POINTER(group_id)); + } + + user->list_op |= (1 << list_id); +} +#endif + +static void +msn_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group) +{ + MsnSession *session; + MsnUserList *userlist; + const char *who; + + session = gc->proto_data; + userlist = session->userlist; + who = msn_normalize(gc->account, buddy->name); + + if (!session->logged_in) + { +#if 0 + fake_userlist_add_buddy(session->sync_userlist, who, MSN_LIST_FL, + group ? group->name : NULL); +#else + purple_debug_error("msn", "msn_add_buddy called before connected\n"); +#endif + + return; + } + +#if 0 + if (group != NULL && group->name != NULL) + purple_debug_info("msn", "msn_add_buddy: %s, %s\n", who, group->name); + else + purple_debug_info("msn", "msn_add_buddy: %s\n", who); +#endif + +#if 0 + /* Which is the max? */ + if (session->fl_users_count >= 150) + { + purple_debug_info("msn", "Too many buddies\n"); + /* Buddy list full */ + /* TODO: purple should be notified of this */ + return; + } +#endif + + /* XXX - Would group ever be NULL here? I don't think so... + * shx: Yes it should; MSN handles non-grouped buddies, and this is only + * internal. */ + msn_userlist_add_buddy(userlist, who, MSN_LIST_FL, + group ? group->name : NULL); +} + +static void +msn_rem_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group) +{ + MsnSession *session; + MsnUserList *userlist; + + session = gc->proto_data; + userlist = session->userlist; + + if (!session->logged_in) + return; + + /* XXX - Does buddy->name need to be msn_normalize'd here? --KingAnt */ + msn_userlist_rem_buddy(userlist, buddy->name, MSN_LIST_FL, group->name); +} + +static void +msn_add_permit(PurpleConnection *gc, const char *who) +{ + MsnSession *session; + MsnUserList *userlist; + MsnUser *user; + + session = gc->proto_data; + userlist = session->userlist; + user = msn_userlist_find_user(userlist, who); + + if (!session->logged_in) + return; + + if (user != NULL && user->list_op & MSN_LIST_BL_OP) + msn_userlist_rem_buddy(userlist, who, MSN_LIST_BL, NULL); + + msn_userlist_add_buddy(userlist, who, MSN_LIST_AL, NULL); +} + +static void +msn_add_deny(PurpleConnection *gc, const char *who) +{ + MsnSession *session; + MsnUserList *userlist; + MsnUser *user; + + session = gc->proto_data; + userlist = session->userlist; + user = msn_userlist_find_user(userlist, who); + + if (!session->logged_in) + return; + + if (user != NULL && user->list_op & MSN_LIST_AL_OP) + msn_userlist_rem_buddy(userlist, who, MSN_LIST_AL, NULL); + + msn_userlist_add_buddy(userlist, who, MSN_LIST_BL, NULL); +} + +static void +msn_rem_permit(PurpleConnection *gc, const char *who) +{ + MsnSession *session; + MsnUserList *userlist; + MsnUser *user; + + session = gc->proto_data; + userlist = session->userlist; + + if (!session->logged_in) + return; + + user = msn_userlist_find_user(userlist, who); + + msn_userlist_rem_buddy(userlist, who, MSN_LIST_AL, NULL); + + if (user != NULL && user->list_op & MSN_LIST_RL_OP) + msn_userlist_add_buddy(userlist, who, MSN_LIST_BL, NULL); +} + +static void +msn_rem_deny(PurpleConnection *gc, const char *who) +{ + MsnSession *session; + MsnUserList *userlist; + MsnUser *user; + + session = gc->proto_data; + userlist = session->userlist; + + if (!session->logged_in) + return; + + user = msn_userlist_find_user(userlist, who); + + msn_userlist_rem_buddy(userlist, who, MSN_LIST_BL, NULL); + + if (user != NULL && user->list_op & MSN_LIST_RL_OP) + msn_userlist_add_buddy(userlist, who, MSN_LIST_AL, NULL); +} + +static void +msn_set_permit_deny(PurpleConnection *gc) +{ + PurpleAccount *account; + MsnSession *session; + MsnCmdProc *cmdproc; + + account = purple_connection_get_account(gc); + session = gc->proto_data; + cmdproc = session->notification->cmdproc; + + if (account->perm_deny == PURPLE_PRIVACY_ALLOW_ALL || + account->perm_deny == PURPLE_PRIVACY_DENY_USERS) + { + msn_cmdproc_send(cmdproc, "BLP", "%s", "AL"); + } + else + { + msn_cmdproc_send(cmdproc, "BLP", "%s", "BL"); + } +} + +static void +msn_chat_invite(PurpleConnection *gc, int id, const char *msg, + const char *who) +{ + MsnSession *session; + MsnSwitchBoard *swboard; + + session = gc->proto_data; + + swboard = msn_session_find_swboard_with_id(session, id); + + if (swboard == NULL) + { + /* if we have no switchboard, everyone else left the chat already */ + swboard = msn_switchboard_new(session); + msn_switchboard_request(swboard); + swboard->chat_id = id; + swboard->conv = purple_find_chat(gc, id); + } + + swboard->flag |= MSN_SB_FLAG_IM; + + msn_switchboard_request_add_user(swboard, who); +} + +static void +msn_chat_leave(PurpleConnection *gc, int id) +{ + MsnSession *session; + MsnSwitchBoard *swboard; + PurpleConversation *conv; + + session = gc->proto_data; + + swboard = msn_session_find_swboard_with_id(session, id); + + /* if swboard is NULL we were the only person left anyway */ + if (swboard == NULL) + return; + + conv = swboard->conv; + + msn_switchboard_release(swboard, MSN_SB_FLAG_IM); + + /* If other switchboards managed to associate themselves with this + * conv, make sure they know it's gone! */ + if (conv != NULL) + { + while ((swboard = msn_session_find_swboard_with_conv(session, conv)) != NULL) + swboard->conv = NULL; + } +} + +static int +msn_chat_send(PurpleConnection *gc, int id, const char *message, PurpleMessageFlags flags) +{ + PurpleAccount *account; + MsnSession *session; + MsnSwitchBoard *swboard; + MsnMessage *msg; + char *msgformat; + char *msgtext; + + account = purple_connection_get_account(gc); + session = gc->proto_data; + swboard = msn_session_find_swboard_with_id(session, id); + + if (swboard == NULL) + return -EINVAL; + + if (!swboard->ready) + return 0; + + swboard->flag |= MSN_SB_FLAG_IM; + + msn_import_html(message, &msgformat, &msgtext); + + if (strlen(msgtext) + strlen(msgformat) + strlen(DISPLAY_VERSION) > 1564) + { + g_free(msgformat); + g_free(msgtext); + + return -E2BIG; + } + + msg = msn_message_new_plain(msgtext); + msn_message_set_attr(msg, "X-MMS-IM-Format", msgformat); + msn_switchboard_send_msg(swboard, msg, FALSE); + msn_message_destroy(msg); + + g_free(msgformat); + g_free(msgtext); + + serv_got_chat_in(gc, id, purple_account_get_username(account), 0, + message, time(NULL)); + + return 0; +} + +static void +msn_keepalive(PurpleConnection *gc) +{ + MsnSession *session; + + session = gc->proto_data; + + if (!session->http_method) + { + MsnCmdProc *cmdproc; + + cmdproc = session->notification->cmdproc; + + msn_cmdproc_send_quick(cmdproc, "PNG", NULL, NULL); + } +} + +static void +msn_group_buddy(PurpleConnection *gc, const char *who, + const char *old_group_name, const char *new_group_name) +{ + MsnSession *session; + MsnUserList *userlist; + + session = gc->proto_data; + userlist = session->userlist; + + msn_userlist_move_buddy(userlist, who, old_group_name, new_group_name); +} + +static void +msn_rename_group(PurpleConnection *gc, const char *old_name, + PurpleGroup *group, GList *moved_buddies) +{ + MsnSession *session; + MsnCmdProc *cmdproc; + int old_gid; + const char *enc_new_group_name; + + session = gc->proto_data; + cmdproc = session->notification->cmdproc; + enc_new_group_name = purple_url_encode(group->name); + + old_gid = msn_userlist_find_group_id(session->userlist, old_name); + + if (old_gid >= 0) + { + msn_cmdproc_send(cmdproc, "REG", "%d %s 0", old_gid, + enc_new_group_name); + } + else + { + msn_cmdproc_send(cmdproc, "ADG", "%s 0", enc_new_group_name); + } +} + +static void +msn_convo_closed(PurpleConnection *gc, const char *who) +{ + MsnSession *session; + MsnSwitchBoard *swboard; + PurpleConversation *conv; + + session = gc->proto_data; + + swboard = msn_session_find_swboard(session, who); + + /* + * Don't perform an assertion here. If swboard is NULL, then the + * switchboard was either closed by the other party, or the person + * is talking to himself. + */ + if (swboard == NULL) + return; + + conv = swboard->conv; + + /* If we release the switchboard here, it may still have messages + pending ACK which would result in incorrect unsent message errors. + Just let it timeout... This is *so* going to screw with people who + use dumb clients that report "User has closed the conversation window" */ + /* msn_switchboard_release(swboard, MSN_SB_FLAG_IM); */ + swboard->conv = NULL; + + /* If other switchboards managed to associate themselves with this + * conv, make sure they know it's gone! */ + if (conv != NULL) + { + while ((swboard = msn_session_find_swboard_with_conv(session, conv)) != NULL) + swboard->conv = NULL; + } +} + +static void +msn_set_buddy_icon(PurpleConnection *gc, PurpleStoredImage *img) +{ + MsnSession *session; + MsnUser *user; + + session = gc->proto_data; + user = session->user; + + msn_user_set_buddy_icon(user, img); + + msn_change_status(session); +} + +static void +msn_remove_group(PurpleConnection *gc, PurpleGroup *group) +{ + MsnSession *session; + MsnCmdProc *cmdproc; + int group_id; + + session = gc->proto_data; + cmdproc = session->notification->cmdproc; + + if ((group_id = msn_userlist_find_group_id(session->userlist, group->name)) >= 0) + { + msn_cmdproc_send(cmdproc, "RMG", "%d", group_id); + } +} + +/** + * Extract info text from info_data and add it to user_info + */ +static gboolean +msn_tooltip_extract_info_text(PurpleNotifyUserInfo *user_info, MsnGetInfoData *info_data) +{ + PurpleBuddy *b; + + b = purple_find_buddy(purple_connection_get_account(info_data->gc), + info_data->name); + + if (b) + { + char *tmp; + + if (b->alias && b->alias[0]) + { + char *aliastext = g_markup_escape_text(b->alias, -1); + purple_notify_user_info_add_pair(user_info, _("Alias"), aliastext); + g_free(aliastext); + } + + if (b->server_alias) + { + char *nicktext = g_markup_escape_text(b->server_alias, -1); + tmp = g_strdup_printf("<font sml=\"msn\">%s</font><br>", nicktext); + purple_notify_user_info_add_pair(user_info, _("Nickname"), tmp); + g_free(tmp); + g_free(nicktext); + } + + /* Add the tooltip information */ + msn_tooltip_text(b, user_info, TRUE); + + return TRUE; + } + + return FALSE; +} + +#if PHOTO_SUPPORT + +static char * +msn_get_photo_url(const char *url_text) +{ + char *p, *q; + + if ((p = strstr(url_text, " contactparams:photopreauthurl=\"")) != NULL) + { + p += strlen(" contactparams:photopreauthurl=\""); + } + + if (p && (strncmp(p, "http://", 8) == 0) && ((q = strchr(p, '"')) != NULL)) + return g_strndup(p, q - p); + + return NULL; +} + +static void msn_got_photo(PurpleUtilFetchUrlData *url_data, gpointer data, + const gchar *url_text, size_t len, const gchar *error_message); + +#endif + +#if 0 +static char *msn_info_date_reformat(const char *field, size_t len) +{ + char *tmp = g_strndup(field, len); + time_t t = purple_str_to_time(tmp, FALSE, NULL, NULL, NULL); + + g_free(tmp); + return g_strdup(purple_date_format_short(localtime(&t))); +} +#endif + +#define MSN_GOT_INFO_GET_FIELD(a, b) \ + found = purple_markup_extract_info_field(stripped, stripped_len, user_info, \ + "\n" a ":", 0, "\n", 0, "Undisclosed", b, 0, NULL, NULL); \ + if (found) \ + sect_info = TRUE; + +#define MSN_GOT_INFO_GET_FIELD_NO_SEARCH(a, b) \ + found = purple_markup_extract_info_field(stripped, stripped_len, user_info, \ + "\n" a ":", 0, "\n", 0, "Undisclosed", b, 0, NULL, msn_info_strip_search_link); \ + if (found) \ + sect_info = TRUE; + +static char * +msn_info_strip_search_link(const char *field, size_t len) +{ + const char *c; + if ((c = strstr(field, " (http://spaces.live.com/default.aspx?page=searchresults")) == NULL && + (c = strstr(field, " (http://spaces.msn.com/default.aspx?page=searchresults")) == NULL) + return g_strndup(field, len); + return g_strndup(field, c - field); +} + +static void +msn_got_info(PurpleUtilFetchUrlData *url_data, gpointer data, + const gchar *url_text, size_t len, const gchar *error_message) +{ + MsnGetInfoData *info_data = (MsnGetInfoData *)data; + PurpleNotifyUserInfo *user_info; + char *stripped, *p, *q, *tmp; + char *user_url = NULL; + gboolean found; + gboolean has_tooltip_text = FALSE; + gboolean has_info = FALSE; + gboolean sect_info = FALSE; + gboolean has_contact_info = FALSE; + char *url_buffer; + int stripped_len; +#if PHOTO_SUPPORT + char *photo_url_text = NULL; + MsnGetInfoStepTwoData *info2_data = NULL; +#endif + + purple_debug_info("msn", "In msn_got_info\n"); + + /* Make sure the connection is still valid */ + if (g_list_find(purple_connections_get_all(), info_data->gc) == NULL) + { + purple_debug_warning("msn", "invalid connection. ignoring buddy info.\n"); + g_free(info_data->name); + g_free(info_data); + return; + } + + user_info = purple_notify_user_info_new(); + has_tooltip_text = msn_tooltip_extract_info_text(user_info, info_data); + + if (error_message != NULL || url_text == NULL || strcmp(url_text, "") == 0) + { + tmp = g_strdup_printf("<b>%s</b>", _("Error retrieving profile")); + purple_notify_user_info_add_pair(user_info, NULL, tmp); + g_free(tmp); + + purple_notify_userinfo(info_data->gc, info_data->name, user_info, NULL, NULL); + purple_notify_user_info_destroy(user_info); + + g_free(info_data->name); + g_free(info_data); + return; + } + + url_buffer = g_strdup(url_text); + + /* If they have a homepage link, MSN masks it such that we need to + * fetch the url out before purple_markup_strip_html() nukes it */ + /* I don't think this works with the new spaces profiles - Stu 3/2/06 */ + if ((p = strstr(url_text, + "Take a look at my </font><A class=viewDesc title=\"")) != NULL) + { + p += 50; + + if ((q = strchr(p, '"')) != NULL) + user_url = g_strndup(p, q - p); + } + + /* + * purple_markup_strip_html() doesn't strip out character entities like + * and · + */ + while ((p = strstr(url_buffer, " ")) != NULL) + { + *p = ' '; /* Turn 's into ordinary blanks */ + p += 1; + memmove(p, p + 5, strlen(p + 5)); + url_buffer[strlen(url_buffer) - 5] = '\0'; + } + + while ((p = strstr(url_buffer, "·")) != NULL) + { + memmove(p, p + 6, strlen(p + 6)); + url_buffer[strlen(url_buffer) - 6] = '\0'; + } + + /* Nuke the nasty \r's that just get in the way */ + purple_str_strip_char(url_buffer, '\r'); + + /* MSN always puts in ' for apostrophes...replace them */ + while ((p = strstr(url_buffer, "'")) != NULL) + { + *p = '\''; + memmove(p + 1, p + 5, strlen(p + 5)); + url_buffer[strlen(url_buffer) - 4] = '\0'; + } + + /* Nuke the html, it's easier than trying to parse the horrid stuff */ + stripped = purple_markup_strip_html(url_buffer); + stripped_len = strlen(stripped); + + purple_debug_misc("msn", "stripped = %p\n", stripped); + purple_debug_misc("msn", "url_buffer = %p\n", url_buffer); + + /* General section header */ + if (has_tooltip_text) + purple_notify_user_info_add_section_break(user_info); + + purple_notify_user_info_add_section_header(user_info, _("General")); + + /* Extract their Name and put it in */ + MSN_GOT_INFO_GET_FIELD("Name", _("Name")); + + /* General */ + MSN_GOT_INFO_GET_FIELD("Nickname", _("Nickname")); + MSN_GOT_INFO_GET_FIELD_NO_SEARCH("Age", _("Age")); + MSN_GOT_INFO_GET_FIELD_NO_SEARCH("Gender", _("Gender")); + MSN_GOT_INFO_GET_FIELD_NO_SEARCH("Occupation", _("Occupation")); + MSN_GOT_INFO_GET_FIELD_NO_SEARCH("Location", _("Location")); + + /* Extract their Interests and put it in */ + found = purple_markup_extract_info_field(stripped, stripped_len, user_info, + "\nInterests\t", 0, " (/default.aspx?page=searchresults", 0, + "Undisclosed", _("Hobbies and Interests") /* _("Interests") */, + 0, NULL, NULL); + + if (found) + sect_info = TRUE; + + MSN_GOT_INFO_GET_FIELD("More about me", _("A Little About Me")); + + if (sect_info) + { + has_info = TRUE; + sect_info = FALSE; + } + else + { + /* Remove the section header */ + purple_notify_user_info_remove_last_item(user_info); + if (has_tooltip_text) + purple_notify_user_info_remove_last_item(user_info); + } + + /* Social */ + purple_notify_user_info_add_section_break(user_info); + purple_notify_user_info_add_section_header(user_info, _("Social")); + + MSN_GOT_INFO_GET_FIELD("Marital status", _("Marital Status")); + MSN_GOT_INFO_GET_FIELD("Interested in", _("Interests")); + MSN_GOT_INFO_GET_FIELD("Pets", _("Pets")); + MSN_GOT_INFO_GET_FIELD("Hometown", _("Hometown")); + MSN_GOT_INFO_GET_FIELD("Places lived", _("Places Lived")); + MSN_GOT_INFO_GET_FIELD("Fashion", _("Fashion")); + MSN_GOT_INFO_GET_FIELD("Humor", _("Humor")); + MSN_GOT_INFO_GET_FIELD("Music", _("Music")); + MSN_GOT_INFO_GET_FIELD("Favorite quote", _("Favorite Quote")); + + if (sect_info) + { + has_info = TRUE; + sect_info = FALSE; + } + else + { + /* Remove the section header */ + purple_notify_user_info_remove_last_item(user_info); + purple_notify_user_info_remove_last_item(user_info); + } + + /* Contact Info */ + /* Personal */ + purple_notify_user_info_add_section_break(user_info); + purple_notify_user_info_add_section_header(user_info, _("Contact Info")); + purple_notify_user_info_add_section_header(user_info, _("Personal")); + + MSN_GOT_INFO_GET_FIELD("Name", _("Name")); + MSN_GOT_INFO_GET_FIELD("Significant other", _("Significant Other")); + MSN_GOT_INFO_GET_FIELD("Home phone", _("Home Phone")); + MSN_GOT_INFO_GET_FIELD("Home phone 2", _("Home Phone 2")); + MSN_GOT_INFO_GET_FIELD("Home address", _("Home Address")); + MSN_GOT_INFO_GET_FIELD("Personal Mobile", _("Personal Mobile")); + MSN_GOT_INFO_GET_FIELD("Home fax", _("Home Fax")); + MSN_GOT_INFO_GET_FIELD("Personal e-mail", _("Personal E-Mail")); + MSN_GOT_INFO_GET_FIELD("Personal IM", _("Personal IM")); + MSN_GOT_INFO_GET_FIELD("Birthday", _("Birthday")); + MSN_GOT_INFO_GET_FIELD("Anniversary", _("Anniversary")); + MSN_GOT_INFO_GET_FIELD("Notes", _("Notes")); + + if (sect_info) + { + has_info = TRUE; + sect_info = FALSE; + has_contact_info = TRUE; + } + else + { + /* Remove the section header */ + purple_notify_user_info_remove_last_item(user_info); + } + + /* Business */ + purple_notify_user_info_add_section_header(user_info, _("Work")); + MSN_GOT_INFO_GET_FIELD("Name", _("Name")); + MSN_GOT_INFO_GET_FIELD("Job title", _("Job Title")); + MSN_GOT_INFO_GET_FIELD("Company", _("Company")); + MSN_GOT_INFO_GET_FIELD("Department", _("Department")); + MSN_GOT_INFO_GET_FIELD("Profession", _("Profession")); + MSN_GOT_INFO_GET_FIELD("Work phone 1", _("Work Phone")); + MSN_GOT_INFO_GET_FIELD("Work phone 2", _("Work Phone 2")); + MSN_GOT_INFO_GET_FIELD("Work address", _("Work Address")); + MSN_GOT_INFO_GET_FIELD("Work mobile", _("Work Mobile")); + MSN_GOT_INFO_GET_FIELD("Work pager", _("Work Pager")); + MSN_GOT_INFO_GET_FIELD("Work fax", _("Work Fax")); + MSN_GOT_INFO_GET_FIELD("Work e-mail", _("Work E-Mail")); + MSN_GOT_INFO_GET_FIELD("Work IM", _("Work IM")); + MSN_GOT_INFO_GET_FIELD("Start date", _("Start Date")); + MSN_GOT_INFO_GET_FIELD("Notes", _("Notes")); + + if (sect_info) + { + has_info = TRUE; + sect_info = FALSE; + has_contact_info = TRUE; + } + else + { + /* Remove the section header */ + purple_notify_user_info_remove_last_item(user_info); + } + + if (!has_contact_info) + { + /* Remove the Contact Info section header */ + purple_notify_user_info_remove_last_item(user_info); + } + +#if 0 /* these probably don't show up any more */ + /* + * The fields, 'A Little About Me', 'Favorite Things', 'Hobbies + * and Interests', 'Favorite Quote', and 'My Homepage' may or may + * not appear, in any combination. However, they do appear in + * certain order, so we can successively search to pin down the + * distinct values. + */ + + /* Check if they have A Little About Me */ + found = purple_markup_extract_info_field(stripped, stripped_len, s, + " A Little About Me \n\n", 0, "Favorite Things", '\n', NULL, + _("A Little About Me"), 0, NULL, NULL); + + if (!found) + { + found = purple_markup_extract_info_field(stripped, stripped_len, s, + " A Little About Me \n\n", 0, "Hobbies and Interests", '\n', + NULL, _("A Little About Me"), 0, NULL, NULL); + } + + if (!found) + { + found = purple_markup_extract_info_field(stripped, stripped_len, s, + " A Little About Me \n\n", 0, "Favorite Quote", '\n', NULL, + _("A Little About Me"), 0, NULL, NULL); + } + + if (!found) + { + found = purple_markup_extract_info_field(stripped, stripped_len, s, + " A Little About Me \n\n", 0, "My Homepage \n\nTake a look", + '\n', + NULL, _("A Little About Me"), 0, NULL, NULL); + } + + if (!found) + { + purple_markup_extract_info_field(stripped, stripped_len, s, + " A Little About Me \n\n", 0, "last updated", '\n', NULL, + _("A Little About Me"), 0, NULL, NULL); + } + + if (found) + has_info = TRUE; + + /* Check if they have Favorite Things */ + found = purple_markup_extract_info_field(stripped, stripped_len, s, + " Favorite Things \n\n", 0, "Hobbies and Interests", '\n', NULL, + _("Favorite Things"), 0, NULL, NULL); + + if (!found) + { + found = purple_markup_extract_info_field(stripped, stripped_len, s, + " Favorite Things \n\n", 0, "Favorite Quote", '\n', NULL, + _("Favorite Things"), 0, NULL, NULL); + } + + if (!found) + { + found = purple_markup_extract_info_field(stripped, stripped_len, s, + " Favorite Things \n\n", 0, "My Homepage \n\nTake a look", '\n', + NULL, _("Favorite Things"), 0, NULL, NULL); + } + + if (!found) + { + purple_markup_extract_info_field(stripped, stripped_len, s, + " Favorite Things \n\n", 0, "last updated", '\n', NULL, + _("Favorite Things"), 0, NULL, NULL); + } + + if (found) + has_info = TRUE; + + /* Check if they have Hobbies and Interests */ + found = purple_markup_extract_info_field(stripped, stripped_len, s, + " Hobbies and Interests \n\n", 0, "Favorite Quote", '\n', NULL, + _("Hobbies and Interests"), 0, NULL, NULL); + + if (!found) + { + found = purple_markup_extract_info_field(stripped, stripped_len, s, + " Hobbies and Interests \n\n", 0, "My Homepage \n\nTake a look", + '\n', NULL, _("Hobbies and Interests"), 0, NULL, NULL); + } + + if (!found) + { + purple_markup_extract_info_field(stripped, stripped_len, s, + " Hobbies and Interests \n\n", 0, "last updated", '\n', NULL, + _("Hobbies and Interests"), 0, NULL, NULL); + } + + if (found) + has_info = TRUE; + + /* Check if they have Favorite Quote */ + found = purple_markup_extract_info_field(stripped, stripped_len, s, + "Favorite Quote \n\n", 0, "My Homepage \n\nTake a look", '\n', NULL, + _("Favorite Quote"), 0, NULL, NULL); + + if (!found) + { + purple_markup_extract_info_field(stripped, stripped_len, s, + "Favorite Quote \n\n", 0, "last updated", '\n', NULL, + _("Favorite Quote"), 0, NULL, NULL); + } + + if (found) + has_info = TRUE; + + /* Extract the last updated date and put it in */ + found = purple_markup_extract_info_field(stripped, stripped_len, s, + " last updated:", 1, "\n", 0, NULL, _("Last Updated"), 0, + NULL, msn_info_date_reformat); + + if (found) + has_info = TRUE; +#endif + + /* If we were able to fetch a homepage url earlier, stick it in there */ + if (user_url != NULL) + { + tmp = g_strdup_printf("<a href=\"%s\">%s</a>", user_url, user_url); + purple_notify_user_info_add_pair(user_info, _("Homepage"), tmp); + g_free(tmp); + g_free(user_url); + + has_info = TRUE; + } + + if (!has_info) + { + /* MSN doesn't actually distinguish between "unknown member" and + * a known member with an empty profile. Try to explain this fact. + * Note that if we have a nonempty tooltip_text, we know the user + * exists. + */ + /* This doesn't work with the new spaces profiles - Stu 3/2/06 + char *p = strstr(url_buffer, "Unknown Member </TITLE>"); + * This might not work for long either ... */ + /* Nope, it failed some time before 5/2/07 :( + char *p = strstr(url_buffer, "form id=\"SpacesSearch\" name=\"SpacesSearch\""); + * Let's see how long this one holds out for ... */ + char *p = strstr(url_buffer, "<form id=\"profile_form\" name=\"profile_form\" action=\"http://spaces.live.com/profile.aspx?cid=0\""); + PurpleBuddy *b = purple_find_buddy + (purple_connection_get_account(info_data->gc), info_data->name); + purple_notify_user_info_add_pair(user_info, _("Error retrieving profile"), + ((p && b) ? _("The user has not created a public profile.") : + (p ? _("MSN reported not being able to find the user's profile. " + "This either means that the user does not exist, " + "or that the user exists " + "but has not created a public profile.") : + _("Could not find " /* This should never happen */ + "any information in the user's profile. " + "The user most likely does not exist.")))); + } + + /* put a link to the actual profile URL */ + tmp = g_strdup_printf("<a href=\"%s%s\">%s%s</a>", + PROFILE_URL, info_data->name, PROFILE_URL, info_data->name); + purple_notify_user_info_add_pair(user_info, _("Profile URL"), tmp); + g_free(tmp); + +#if PHOTO_SUPPORT + /* Find the URL to the photo; must be before the marshalling [Bug 994207] */ + photo_url_text = msn_get_photo_url(url_text); + + /* Marshall the existing state */ + info2_data = g_malloc0(sizeof(MsnGetInfoStepTwoData)); + info2_data->info_data = info_data; + info2_data->stripped = stripped; + info2_data->url_buffer = url_buffer; + info2_data->user_info = user_info; + info2_data->photo_url_text = photo_url_text; + + /* Try to put the photo in there too, if there's one */ + if (photo_url_text) + { + purple_util_fetch_url(photo_url_text, FALSE, NULL, FALSE, msn_got_photo, + info2_data); + } + else + { + /* Emulate a callback */ + /* TODO: Huh? */ + msn_got_photo(NULL, info2_data, NULL, 0, NULL); + } +} + +static void +msn_got_photo(PurpleUtilFetchUrlData *url_data, gpointer user_data, + const gchar *url_text, size_t len, const gchar *error_message) +{ + MsnGetInfoStepTwoData *info2_data = (MsnGetInfoStepTwoData *)user_data; + int id = -1; + + /* Unmarshall the saved state */ + MsnGetInfoData *info_data = info2_data->info_data; + char *stripped = info2_data->stripped; + char *url_buffer = info2_data->url_buffer; + PurpleNotifyUserInfo *user_info = info2_data->user_info; + char *photo_url_text = info2_data->photo_url_text; + + /* Make sure the connection is still valid if we got here by fetching a photo url */ + if (url_text && (error_message != NULL || + g_list_find(purple_connections_get_all(), info_data->gc) == NULL)) + { + purple_debug_warning("msn", "invalid connection. ignoring buddy photo info.\n"); + g_free(stripped); + g_free(url_buffer); + g_free(user_info); + g_free(info_data->name); + g_free(info_data); + g_free(photo_url_text); + g_free(info2_data); + + return; + } + + /* Try to put the photo in there too, if there's one and is readable */ + if (user_data && url_text && len != 0) + { + if (strstr(url_text, "400 Bad Request") + || strstr(url_text, "403 Forbidden") + || strstr(url_text, "404 Not Found")) + { + + purple_debug_info("msn", "Error getting %s: %s\n", + photo_url_text, url_text); + } + else + { + char buf[1024]; + purple_debug_info("msn", "%s is %d bytes\n", photo_url_text, len); + id = purple_imgstore_add_with_id(g_memdup(url_text, len), len, NULL); + g_snprintf(buf, sizeof(buf), "<img id=\"%d\"><br>", id); + purple_notify_user_info_prepend_pair(user_info, NULL, buf); + } + } + + /* We continue here from msn_got_info, as if nothing has happened */ +#endif + purple_notify_userinfo(info_data->gc, info_data->name, user_info, NULL, NULL); + + g_free(stripped); + g_free(url_buffer); + purple_notify_user_info_destroy(user_info); + g_free(info_data->name); + g_free(info_data); +#if PHOTO_SUPPORT + g_free(photo_url_text); + g_free(info2_data); + if (id != -1) + purple_imgstore_unref_by_id(id); +#endif +} + +static void +msn_get_info(PurpleConnection *gc, const char *name) +{ + MsnGetInfoData *data; + char *url; + + data = g_new0(MsnGetInfoData, 1); + data->gc = gc; + data->name = g_strdup(name); + + url = g_strdup_printf("%s%s", PROFILE_URL, name); + + purple_util_fetch_url(url, FALSE, + "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)", + TRUE, msn_got_info, data); + + g_free(url); +} + +static gboolean msn_load(PurplePlugin *plugin) +{ + msn_notification_init(); + msn_switchboard_init(); + msn_sync_init(); + + return TRUE; +} + +static gboolean msn_unload(PurplePlugin *plugin) +{ + msn_notification_end(); + msn_switchboard_end(); + msn_sync_end(); + + return TRUE; +} + +static PurpleAccount *find_acct(const char *prpl, const char *acct_id) +{ + PurpleAccount *acct = NULL; + + /* If we have a specific acct, use it */ + if (acct_id) { + acct = purple_accounts_find(acct_id, prpl); + if (acct && !purple_account_is_connected(acct)) + acct = NULL; + } else { /* Otherwise find an active account for the protocol */ + GList *l = purple_accounts_get_all(); + while (l) { + if (!strcmp(prpl, purple_account_get_protocol_id(l->data)) + && purple_account_is_connected(l->data)) { + acct = l->data; + break; + } + l = l->next; + } + } + + return acct; +} + +static gboolean msn_uri_handler(const char *proto, const char *cmd, GHashTable *params) +{ + char *acct_id = g_hash_table_lookup(params, "account"); + PurpleAccount *acct; + + if (g_ascii_strcasecmp(proto, "msnim")) + return FALSE; + + acct = find_acct("prpl-msn", acct_id); + + if (!acct) + return FALSE; + + /* msnim:chat?contact=user@domain.tld */ + if (!g_ascii_strcasecmp(cmd, "Chat")) { + char *sname = g_hash_table_lookup(params, "contact"); + if (sname) { + PurpleConversation *conv = purple_find_conversation_with_account( + PURPLE_CONV_TYPE_IM, sname, acct); + if (conv == NULL) + conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, sname); + purple_conversation_present(conv); + } + /*else + **If pidgindialogs_im() was in the core, we could use it here. + * It is all purple_request_* based, but I'm not sure it really belongs in the core + pidgindialogs_im();*/ + + return TRUE; + } + /* msnim:add?contact=user@domain.tld */ + else if (!g_ascii_strcasecmp(cmd, "Add")) { + char *name = g_hash_table_lookup(params, "contact"); + purple_blist_request_add_buddy(acct, name, NULL, NULL); + return TRUE; + } + + return FALSE; +} + + +static PurplePluginProtocolInfo prpl_info = +{ + OPT_PROTO_MAIL_CHECK, + NULL, /* user_splits */ + NULL, /* protocol_options */ + {"png", 0, 0, 96, 96, 0, PURPLE_ICON_SCALE_SEND}, /* icon_spec */ + msn_list_icon, /* list_icon */ + NULL, /* list_emblems */ + msn_status_text, /* status_text */ + msn_tooltip_text, /* tooltip_text */ + msn_status_types, /* away_states */ + msn_blist_node_menu, /* blist_node_menu */ + NULL, /* chat_info */ + NULL, /* chat_info_defaults */ + msn_login, /* login */ + msn_close, /* close */ + msn_send_im, /* send_im */ + NULL, /* set_info */ + msn_send_typing, /* send_typing */ + msn_get_info, /* get_info */ + msn_set_status, /* set_away */ + msn_set_idle, /* set_idle */ + NULL, /* change_passwd */ + msn_add_buddy, /* add_buddy */ + NULL, /* add_buddies */ + msn_rem_buddy, /* remove_buddy */ + NULL, /* remove_buddies */ + msn_add_permit, /* add_permit */ + msn_add_deny, /* add_deny */ + msn_rem_permit, /* rem_permit */ + msn_rem_deny, /* rem_deny */ + msn_set_permit_deny, /* set_permit_deny */ + NULL, /* join_chat */ + NULL, /* reject chat invite */ + NULL, /* get_chat_name */ + msn_chat_invite, /* chat_invite */ + msn_chat_leave, /* chat_leave */ + NULL, /* chat_whisper */ + msn_chat_send, /* chat_send */ + msn_keepalive, /* keepalive */ + NULL, /* register_user */ + NULL, /* get_cb_info */ + NULL, /* get_cb_away */ + NULL, /* alias_buddy */ + msn_group_buddy, /* group_buddy */ + msn_rename_group, /* rename_group */ + NULL, /* buddy_free */ + msn_convo_closed, /* convo_closed */ + msn_normalize, /* normalize */ + msn_set_buddy_icon, /* set_buddy_icon */ + msn_remove_group, /* remove_group */ + NULL, /* get_cb_real_name */ + NULL, /* set_chat_topic */ + NULL, /* find_blist_chat */ + NULL, /* roomlist_get_list */ + NULL, /* roomlist_cancel */ + NULL, /* roomlist_expand_category */ + msn_can_receive_file, /* can_receive_file */ + msn_send_file, /* send_file */ + msn_new_xfer, /* new_xfer */ + msn_offline_message, /* offline_message */ + 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 +}; + +static PurplePluginInfo info = +{ + PURPLE_PLUGIN_MAGIC, + PURPLE_MAJOR_VERSION, + PURPLE_MINOR_VERSION, + PURPLE_PLUGIN_PROTOCOL, /**< type */ + NULL, /**< ui_requirement */ + 0, /**< flags */ + NULL, /**< dependencies */ + PURPLE_PRIORITY_DEFAULT, /**< priority */ + + "prpl-msn", /**< id */ + "MSN", /**< name */ + DISPLAY_VERSION, /**< version */ + /** summary */ + N_("MSN Protocol Plugin"), + /** description */ + N_("MSN Protocol Plugin"), + "Christian Hammond <chipx86@gnupdate.org>", /**< author */ + PURPLE_WEBSITE, /**< homepage */ + + msn_load, /**< load */ + msn_unload, /**< unload */ + NULL, /**< destroy */ + + NULL, /**< ui_info */ + &prpl_info, /**< extra_info */ + NULL, /**< prefs_info */ + msn_actions, + + /* padding */ + NULL, + NULL, + NULL, + NULL +}; + +static void +init_plugin(PurplePlugin *plugin) +{ + PurpleAccountOption *option; + + option = purple_account_option_string_new(_("Server"), "server", + MSN_SERVER); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, + option); + + option = purple_account_option_int_new(_("Port"), "port", 1863); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, + option); + + option = purple_account_option_bool_new(_("Use HTTP Method"), + "http_method", FALSE); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, + option); + + option = purple_account_option_string_new(_("HTTP Method Server"), + "http_method_server", MSN_HTTPCONN_SERVER); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, + option); + + option = purple_account_option_bool_new(_("Show custom smileys"), + "custom_smileys", TRUE); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, + option); + + purple_cmd_register("nudge", "", PURPLE_CMD_P_PRPL, + PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_PRPL_ONLY, + "prpl-msn", msn_cmd_nudge, + _("nudge: nudge a user to get their attention"), NULL); + + purple_prefs_remove("/plugins/prpl/msn"); + + purple_signal_connect(purple_get_core(), "uri-handler", plugin, + PURPLE_CALLBACK(msn_uri_handler), NULL); +} + +PURPLE_INIT_PLUGIN(msn, init_plugin, info);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/msn.h Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,134 @@ +/** + * @file msn.h The MSN protocol plugin + * + * 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 _MSN_H_ +#define _MSN_H_ + +/* #define MSN_DEBUG_MSG 1 */ +/* #define MSN_DEBUG_SLPMSG 1 */ +/* #define MSN_DEBUG_HTTP 1 */ + +/* #define MSN_DEBUG_SLP 1 */ +/* #define MSN_DEBUG_SLP_VERBOSE 1 */ +/* #define MSN_DEBUG_SLP_FILES 1 */ + +/* #define MSN_DEBUG_NS 1 */ +/* #define MSN_DEBUG_SB 1 */ + +#include "internal.h" + +#include "account.h" +#include "accountopt.h" +#include "blist.h" +#include "connection.h" +#include "conversation.h" +#include "debug.h" +#include "cipher.h" +#include "notify.h" +#include "privacy.h" +#include "proxy.h" +#include "prpl.h" +#include "request.h" +#include "servconn.h" +#include "sslconn.h" +#include "util.h" + +#include "ft.h" + +#define MSN_BUF_LEN 8192 + +#define USEROPT_MSNSERVER 3 +#define MSN_SERVER "messenger.hotmail.com" +#define MSN_HTTPCONN_SERVER "gateway.messenger.hotmail.com" +#define USEROPT_MSNPORT 4 +#define MSN_PORT 1863 + +#define MSN_TYPING_RECV_TIMEOUT 6 +#define MSN_TYPING_SEND_TIMEOUT 4 + +#define HOTMAIL_URL "http://www.hotmail.com/cgi-bin/folders" +#define PASSPORT_URL "http://lc1.law13.hotmail.passport.com/cgi-bin/dologin?login=" +#define PROFILE_URL "http://spaces.live.com/profile.aspx?mem=" + +#define USEROPT_HOTMAIL 0 + +#define BUDDY_ALIAS_MAXLEN 387 + +#define MSN_FT_GUID "{5D3E02AB-6190-11d3-BBBB-00C04F795683}" + +#define MSN_CLIENTINFO \ + "Client-Name: Purple/" VERSION "\r\n" \ + "Chat-Logging: Y\r\n" + +/* Index into attention_types */ +#define MSN_NUDGE 0 + +typedef enum +{ + MSN_LIST_FL_OP = 0x01, + MSN_LIST_AL_OP = 0x02, + MSN_LIST_BL_OP = 0x04, + MSN_LIST_RL_OP = 0x08 + +} MsnListOp; + +typedef enum +{ + MSN_CLIENT_CAP_WIN_MOBILE = 0x00001, + MSN_CLIENT_CAP_UNKNOWN_1 = 0x00002, + MSN_CLIENT_CAP_INK_GIF = 0x00004, + MSN_CLIENT_CAP_INK_ISF = 0x00008, + MSN_CLIENT_CAP_VIDEO_CHAT = 0x00010, + MSN_CLIENT_CAP_BASE = 0x00020, + MSN_CLIENT_CAP_MSNMOBILE = 0x00040, + MSN_CLIENT_CAP_MSNDIRECT = 0x00080, + MSN_CLIENT_CAP_WEBMSGR = 0x00100, + MSN_CLIENT_CAP_DIRECTIM = 0x04000, + MSN_CLIENT_CAP_WINKS = 0x08000, + MSN_CLIENT_CAP_SEARCH = 0x10000 + +} MsnClientCaps; + +typedef enum +{ + MSN_CLIENT_VER_5_0 = 0x00, + MSN_CLIENT_VER_6_0 = 0x10, /* MSNC1 */ + MSN_CLIENT_VER_6_1 = 0x20, /* MSNC2 */ + MSN_CLIENT_VER_6_2 = 0x30, /* MSNC3 */ + MSN_CLIENT_VER_7_0 = 0x40, /* MSNC4 */ + MSN_CLIENT_VER_7_5 = 0x50 /* MSNC5 */ + +} MsnClientVerId; + +#define MSN_CLIENT_ID_VERSION MSN_CLIENT_VER_7_0 +#define MSN_CLIENT_ID_RESERVED_1 0x00 +#define MSN_CLIENT_ID_RESERVED_2 0x00 +#define MSN_CLIENT_ID_CAPABILITIES MSN_CLIENT_CAP_BASE + +#define MSN_CLIENT_ID \ + ((MSN_CLIENT_ID_VERSION << 24) | \ + (MSN_CLIENT_ID_RESERVED_1 << 16) | \ + (MSN_CLIENT_ID_RESERVED_2 << 8) | \ + (MSN_CLIENT_ID_CAPABILITIES)) + +#endif /* _MSN_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/nexus.c Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,511 @@ +/** + * @file nexus.c MSN Nexus functions + * + * 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 "msn.h" +#include "nexus.h" +#include "notification.h" + +/************************************************************************** + * Main + **************************************************************************/ + +MsnNexus * +msn_nexus_new(MsnSession *session) +{ + MsnNexus *nexus; + + nexus = g_new0(MsnNexus, 1); + nexus->session = session; + nexus->challenge_data = g_hash_table_new_full(g_str_hash, + g_str_equal, g_free, g_free); + + return nexus; +} + +void +msn_nexus_destroy(MsnNexus *nexus) +{ + if (nexus->gsc) + purple_ssl_close(nexus->gsc); + + g_free(nexus->login_host); + + g_free(nexus->login_path); + + if (nexus->challenge_data != NULL) + g_hash_table_destroy(nexus->challenge_data); + + if (nexus->input_handler > 0) + purple_input_remove(nexus->input_handler); + g_free(nexus->write_buf); + g_free(nexus->read_buf); + + g_free(nexus); +} + +/************************************************************************** + * Util + **************************************************************************/ + +static gssize +msn_ssl_read(MsnNexus *nexus) +{ + gssize len; + char temp_buf[4096]; + + if ((len = purple_ssl_read(nexus->gsc, temp_buf, + sizeof(temp_buf))) > 0) + { + nexus->read_buf = g_realloc(nexus->read_buf, + nexus->read_len + len + 1); + strncpy(nexus->read_buf + nexus->read_len, temp_buf, len); + nexus->read_len += len; + nexus->read_buf[nexus->read_len] = '\0'; + } + + return len; +} + +static void +nexus_write_cb(gpointer data, gint source, PurpleInputCondition cond) +{ + MsnNexus *nexus = data; + int len, total_len; + + total_len = strlen(nexus->write_buf); + + len = purple_ssl_write(nexus->gsc, + nexus->write_buf + nexus->written_len, + total_len - nexus->written_len); + + if (len < 0 && errno == EAGAIN) + return; + else if (len <= 0) { + purple_input_remove(nexus->input_handler); + nexus->input_handler = 0; + /* TODO: notify of the error */ + return; + } + nexus->written_len += len; + + if (nexus->written_len < total_len) + return; + + purple_input_remove(nexus->input_handler); + nexus->input_handler = 0; + + g_free(nexus->write_buf); + nexus->write_buf = NULL; + nexus->written_len = 0; + + nexus->written_cb(nexus, source, 0); +} + +/************************************************************************** + * Login + **************************************************************************/ + +static void +login_connect_cb(gpointer data, PurpleSslConnection *gsc, + PurpleInputCondition cond); + +static void +login_error_cb(PurpleSslConnection *gsc, PurpleSslErrorType error, void *data) +{ + MsnNexus *nexus; + MsnSession *session; + + nexus = data; + g_return_if_fail(nexus != NULL); + + nexus->gsc = NULL; + + session = nexus->session; + g_return_if_fail(session != NULL); + + msn_session_set_error(session, MSN_ERROR_AUTH, _("Unable to connect")); + /* the above line will result in nexus being destroyed, so we don't want + * to destroy it here, or we'd crash */ +} + +static void +nexus_login_written_cb(gpointer data, gint source, PurpleInputCondition cond) +{ + MsnNexus *nexus = data; + MsnSession *session; + int len; + + session = nexus->session; + g_return_if_fail(session != NULL); + + if (nexus->input_handler == 0) + /* TODO: Use purple_ssl_input_add()? */ + nexus->input_handler = purple_input_add(nexus->gsc->fd, + PURPLE_INPUT_READ, nexus_login_written_cb, nexus); + + + len = msn_ssl_read(nexus); + + if (len < 0 && errno == EAGAIN) + return; + else if (len < 0) { + purple_input_remove(nexus->input_handler); + nexus->input_handler = 0; + g_free(nexus->read_buf); + nexus->read_buf = NULL; + nexus->read_len = 0; + /* TODO: error handling */ + return; + } + + if (g_strstr_len(nexus->read_buf, nexus->read_len, + "\r\n\r\n") == NULL) + return; + + purple_input_remove(nexus->input_handler); + nexus->input_handler = 0; + + purple_ssl_close(nexus->gsc); + nexus->gsc = NULL; + + purple_debug_misc("msn", "ssl buffer: {%s}\n", nexus->read_buf); + + if (strstr(nexus->read_buf, "HTTP/1.1 302") != NULL) + { + /* Redirect. */ + char *location, *c; + + location = strstr(nexus->read_buf, "Location: "); + if (location == NULL) + { + g_free(nexus->read_buf); + nexus->read_buf = NULL; + nexus->read_len = 0; + + return; + } + location = strchr(location, ' ') + 1; + + if ((c = strchr(location, '\r')) != NULL) + *c = '\0'; + + /* Skip the http:// */ + if ((c = strchr(location, '/')) != NULL) + location = c + 2; + + if ((c = strchr(location, '/')) != NULL) + { + g_free(nexus->login_path); + nexus->login_path = g_strdup(c); + + *c = '\0'; + } + + g_free(nexus->login_host); + nexus->login_host = g_strdup(location); + + nexus->gsc = purple_ssl_connect(session->account, + nexus->login_host, PURPLE_SSL_DEFAULT_PORT, + login_connect_cb, login_error_cb, nexus); + } + else if (strstr(nexus->read_buf, "HTTP/1.1 401 Unauthorized") != NULL) + { + const char *error; + + if ((error = strstr(nexus->read_buf, "WWW-Authenticate")) != NULL) + { + if ((error = strstr(error, "cbtxt=")) != NULL) + { + const char *c; + char *temp; + + error += strlen("cbtxt="); + + if ((c = strchr(error, '\n')) == NULL) + c = error + strlen(error); + + temp = g_strndup(error, c - error); + error = purple_url_decode(temp); + g_free(temp); + if ((temp = strstr(error, " Do one of the following or try again:")) != NULL) + *temp = '\0'; + } + } + + msn_session_set_error(session, MSN_ERROR_AUTH, error); + } + else if (strstr(nexus->read_buf, "HTTP/1.1 503 Service Unavailable")) + { + msn_session_set_error(session, MSN_ERROR_SERV_UNAVAILABLE, NULL); + } + else if (strstr(nexus->read_buf, "HTTP/1.1 200 OK")) + { + char *base, *c; + char *login_params; + +#if 0 + /* All your base are belong to us. */ + base = buffer; + + /* For great cookie! */ + while ((base = strstr(base, "Set-Cookie: ")) != NULL) + { + base += strlen("Set-Cookie: "); + + c = strchr(base, ';'); + + session->login_cookies = + g_list_append(session->login_cookies, + g_strndup(base, c - base)); + } +#endif + + base = strstr(nexus->read_buf, "Authentication-Info: "); + + g_return_if_fail(base != NULL); + + base = strstr(base, "from-PP='"); + base += strlen("from-PP='"); + c = strchr(base, '\''); + + login_params = g_strndup(base, c - base); + + msn_got_login_params(session, login_params); + + g_free(login_params); + + msn_nexus_destroy(nexus); + session->nexus = NULL; + return; + } + + g_free(nexus->read_buf); + nexus->read_buf = NULL; + nexus->read_len = 0; + +} + +/* this guards against missing hash entries */ +static char * +nexus_challenge_data_lookup(GHashTable *challenge_data, const char *key) +{ + char *entry; + + return (entry = (char *)g_hash_table_lookup(challenge_data, key)) ? + entry : "(null)"; +} + +void +login_connect_cb(gpointer data, PurpleSslConnection *gsc, + PurpleInputCondition cond) +{ + MsnNexus *nexus; + MsnSession *session; + char *username, *password; + char *request_str, *head, *tail; + char *buffer = NULL; + guint32 ctint; + + nexus = data; + g_return_if_fail(nexus != NULL); + + session = nexus->session; + g_return_if_fail(session != NULL); + + msn_session_set_login_step(session, MSN_LOGIN_STEP_GET_COOKIE); + + username = + g_strdup(purple_url_encode(purple_account_get_username(session->account))); + + password = + g_strndup(purple_url_encode(purple_connection_get_password(session->account->gc)), 16); + + ctint = strtoul((char *)g_hash_table_lookup(nexus->challenge_data, "ct"), NULL, 10) + 200; + + head = g_strdup_printf( + "GET %s HTTP/1.1\r\n" + "Authorization: Passport1.4 OrgVerb=GET,OrgURL=%s,sign-in=%s", + nexus->login_path, + (char *)g_hash_table_lookup(nexus->challenge_data, "ru"), + username); + + tail = g_strdup_printf( + "lc=%s,id=%s,tw=%s,fs=%s,ru=%s,ct=%" G_GUINT32_FORMAT ",kpp=%s,kv=%s,ver=%s,tpf=%s\r\n" + "User-Agent: MSMSGS\r\n" + "Host: %s\r\n" + "Connection: Keep-Alive\r\n" + "Cache-Control: no-cache\r\n", + nexus_challenge_data_lookup(nexus->challenge_data, "lc"), + nexus_challenge_data_lookup(nexus->challenge_data, "id"), + nexus_challenge_data_lookup(nexus->challenge_data, "tw"), + nexus_challenge_data_lookup(nexus->challenge_data, "fs"), + nexus_challenge_data_lookup(nexus->challenge_data, "ru"), + ctint, + nexus_challenge_data_lookup(nexus->challenge_data, "kpp"), + nexus_challenge_data_lookup(nexus->challenge_data, "kv"), + nexus_challenge_data_lookup(nexus->challenge_data, "ver"), + nexus_challenge_data_lookup(nexus->challenge_data, "tpf"), + nexus->login_host); + + buffer = g_strdup_printf("%s,pwd=XXXXXXXX,%s\r\n", head, tail); + request_str = g_strdup_printf("%s,pwd=%s,%s\r\n", head, password, tail); + + purple_debug_misc("msn", "Sending: {%s}\n", buffer); + + g_free(buffer); + g_free(head); + g_free(tail); + g_free(username); + g_free(password); + + nexus->write_buf = request_str; + nexus->written_len = 0; + + nexus->read_len = 0; + + nexus->written_cb = nexus_login_written_cb; + + nexus->input_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, + nexus_write_cb, nexus); + + nexus_write_cb(nexus, gsc->fd, PURPLE_INPUT_WRITE); + + return; + + +} + +static void +nexus_connect_written_cb(gpointer data, gint source, PurpleInputCondition cond) +{ + MsnNexus *nexus = data; + int len; + char *da_login; + char *base, *c; + + if (nexus->input_handler == 0) + /* TODO: Use purple_ssl_input_add()? */ + nexus->input_handler = purple_input_add(nexus->gsc->fd, + PURPLE_INPUT_READ, nexus_connect_written_cb, nexus); + + /* Get the PassportURLs line. */ + len = msn_ssl_read(nexus); + + if (len < 0 && errno == EAGAIN) + return; + else if (len < 0) { + purple_input_remove(nexus->input_handler); + nexus->input_handler = 0; + g_free(nexus->read_buf); + nexus->read_buf = NULL; + nexus->read_len = 0; + /* TODO: error handling */ + return; + } + + if (g_strstr_len(nexus->read_buf, nexus->read_len, + "\r\n\r\n") == NULL) + return; + + purple_input_remove(nexus->input_handler); + nexus->input_handler = 0; + + base = strstr(nexus->read_buf, "PassportURLs"); + + if (base == NULL) + { + g_free(nexus->read_buf); + nexus->read_buf = NULL; + nexus->read_len = 0; + return; + } + + if ((da_login = strstr(base, "DALogin=")) != NULL) + { + /* skip over "DALogin=" */ + da_login += 8; + + if ((c = strchr(da_login, ',')) != NULL) + *c = '\0'; + + if ((c = strchr(da_login, '/')) != NULL) + { + nexus->login_path = g_strdup(c); + *c = '\0'; + } + + nexus->login_host = g_strdup(da_login); + } + + g_free(nexus->read_buf); + nexus->read_buf = NULL; + nexus->read_len = 0; + + purple_ssl_close(nexus->gsc); + + /* Now begin the connection to the login server. */ + nexus->gsc = purple_ssl_connect(nexus->session->account, + nexus->login_host, PURPLE_SSL_DEFAULT_PORT, + login_connect_cb, login_error_cb, nexus); +} + + +/************************************************************************** + * Connect + **************************************************************************/ + +static void +nexus_connect_cb(gpointer data, PurpleSslConnection *gsc, + PurpleInputCondition cond) +{ + MsnNexus *nexus; + MsnSession *session; + + nexus = data; + g_return_if_fail(nexus != NULL); + + session = nexus->session; + g_return_if_fail(session != NULL); + + msn_session_set_login_step(session, MSN_LOGIN_STEP_AUTH); + + nexus->write_buf = g_strdup("GET /rdr/pprdr.asp\r\n\r\n"); + nexus->written_len = 0; + + nexus->read_len = 0; + + nexus->written_cb = nexus_connect_written_cb; + + nexus->input_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, + nexus_write_cb, nexus); + + nexus_write_cb(nexus, gsc->fd, PURPLE_INPUT_WRITE); +} + +void +msn_nexus_connect(MsnNexus *nexus) +{ + nexus->gsc = purple_ssl_connect(nexus->session->account, + "nexus.passport.com", PURPLE_SSL_DEFAULT_PORT, + nexus_connect_cb, login_error_cb, nexus); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/nexus.h Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,52 @@ +/** + * @file nexus.h MSN Nexus functions + * + * 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 _MSN_NEXUS_H_ +#define _MSN_NEXUS_H_ + +typedef struct _MsnNexus MsnNexus; + +struct _MsnNexus +{ + MsnSession *session; + + char *login_host; + char *login_path; + GHashTable *challenge_data; + PurpleSslConnection *gsc; + + guint input_handler; + + char *write_buf; + gsize written_len; + PurpleInputFunction written_cb; + + char *read_buf; + gsize read_len; +}; + +void msn_nexus_connect(MsnNexus *nexus); +MsnNexus *msn_nexus_new(MsnSession *session); +void msn_nexus_destroy(MsnNexus *nexus); + +#endif /* _MSN_NEXUS_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/notification.c Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,1464 @@ +/** + * @file notification.c Notification server functions + * + * 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 "msn.h" +#include "notification.h" +#include "state.h" +#include "error.h" +#include "msn-utils.h" +#include "page.h" + +#include "userlist.h" +#include "sync.h" +#include "slplink.h" + +static MsnTable *cbs_table; + +/************************************************************************** + * Main + **************************************************************************/ + +static void +destroy_cb(MsnServConn *servconn) +{ + MsnNotification *notification; + + notification = servconn->cmdproc->data; + g_return_if_fail(notification != NULL); + + msn_notification_destroy(notification); +} + +MsnNotification * +msn_notification_new(MsnSession *session) +{ + MsnNotification *notification; + MsnServConn *servconn; + + g_return_val_if_fail(session != NULL, NULL); + + notification = g_new0(MsnNotification, 1); + + notification->session = session; + notification->servconn = servconn = msn_servconn_new(session, MSN_SERVCONN_NS); + msn_servconn_set_destroy_cb(servconn, destroy_cb); + + notification->cmdproc = servconn->cmdproc; + notification->cmdproc->data = notification; + notification->cmdproc->cbs_table = cbs_table; + + return notification; +} + +void +msn_notification_destroy(MsnNotification *notification) +{ + notification->cmdproc->data = NULL; + + msn_servconn_set_destroy_cb(notification->servconn, NULL); + + msn_servconn_destroy(notification->servconn); + + g_free(notification); +} + +/************************************************************************** + * Connect + **************************************************************************/ + +static void +connect_cb(MsnServConn *servconn) +{ + MsnCmdProc *cmdproc; + MsnSession *session; + PurpleAccount *account; + char **a, **c, *vers; + int i; + + g_return_if_fail(servconn != NULL); + + cmdproc = servconn->cmdproc; + session = servconn->session; + account = session->account; + + /* Allocate an array for CVR0, NULL, and all the versions */ + a = c = g_new0(char *, session->protocol_ver - 8 + 3); + + for (i = session->protocol_ver; i >= 8; i--) + *c++ = g_strdup_printf("MSNP%d", i); + + *c++ = g_strdup("CVR0"); + + vers = g_strjoinv(" ", a); + + if (session->login_step == MSN_LOGIN_STEP_START) + msn_session_set_login_step(session, MSN_LOGIN_STEP_HANDSHAKE); + else + msn_session_set_login_step(session, MSN_LOGIN_STEP_HANDSHAKE2); + + msn_cmdproc_send(cmdproc, "VER", "%s", vers); + + g_strfreev(a); + g_free(vers); +} + +gboolean +msn_notification_connect(MsnNotification *notification, const char *host, int port) +{ + MsnServConn *servconn; + + g_return_val_if_fail(notification != NULL, FALSE); + + servconn = notification->servconn; + + msn_servconn_set_connect_cb(servconn, connect_cb); + notification->in_use = msn_servconn_connect(servconn, host, port); + + return notification->in_use; +} + +void +msn_notification_disconnect(MsnNotification *notification) +{ + g_return_if_fail(notification != NULL); + g_return_if_fail(notification->in_use); + + msn_servconn_disconnect(notification->servconn); + + notification->in_use = FALSE; +} + +/************************************************************************** + * Util + **************************************************************************/ + +static void +group_error_helper(MsnSession *session, const char *msg, int group_id, int error) +{ + PurpleAccount *account; + PurpleConnection *gc; + char *reason = NULL; + char *title = NULL; + + account = session->account; + gc = purple_account_get_connection(account); + + if (error == 224) + { + if (group_id == 0) + { + return; + } + else + { + const char *group_name; + group_name = + msn_userlist_find_group_name(session->userlist, + group_id); + reason = g_strdup_printf(_("%s is not a valid group."), + group_name); + } + } + else + { + reason = g_strdup(_("Unknown error.")); + } + + title = g_strdup_printf(_("%s on %s (%s)"), msg, + purple_account_get_username(account), + purple_account_get_protocol_name(account)); + purple_notify_error(gc, NULL, title, reason); + g_free(title); + g_free(reason); +} + +/************************************************************************** + * Login + **************************************************************************/ + +void +msn_got_login_params(MsnSession *session, const char *login_params) +{ + MsnCmdProc *cmdproc; + + cmdproc = session->notification->cmdproc; + + msn_session_set_login_step(session, MSN_LOGIN_STEP_AUTH_END); + + msn_cmdproc_send(cmdproc, "USR", "TWN S %s", login_params); +} + +static void +cvr_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + PurpleAccount *account; + + account = cmdproc->session->account; + + msn_cmdproc_send(cmdproc, "USR", "TWN I %s", + purple_account_get_username(account)); +} + +static void +usr_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnSession *session; + PurpleAccount *account; + PurpleConnection *gc; + + session = cmdproc->session; + account = session->account; + gc = purple_account_get_connection(account); + + if (!g_ascii_strcasecmp(cmd->params[1], "OK")) + { + /* OK */ + const char *friendly = purple_url_decode(cmd->params[3]); + + purple_connection_set_display_name(gc, friendly); + + msn_session_set_login_step(session, MSN_LOGIN_STEP_SYN); + + msn_cmdproc_send(cmdproc, "SYN", "%s", "0"); + } + else if (!g_ascii_strcasecmp(cmd->params[1], "TWN")) + { + /* Passport authentication */ + char **elems, **cur, **tokens; + + session->nexus = msn_nexus_new(session); + + /* Parse the challenge data. */ + + elems = g_strsplit(cmd->params[3], ",", 0); + + for (cur = elems; *cur != NULL; cur++) + { + tokens = g_strsplit(*cur, "=", 2); + g_hash_table_insert(session->nexus->challenge_data, tokens[0], tokens[1]); + /* Don't free each of the tokens, only the array. */ + g_free(tokens); + } + + g_strfreev(elems); + + msn_session_set_login_step(session, MSN_LOGIN_STEP_AUTH_START); + + msn_nexus_connect(session->nexus); + } +} + +static void +usr_error(MsnCmdProc *cmdproc, MsnTransaction *trans, int error) +{ + MsnErrorType msnerr = 0; + + switch (error) + { + case 500: + case 601: + case 910: + case 921: + msnerr = MSN_ERROR_SERV_UNAVAILABLE; + break; + case 911: + msnerr = MSN_ERROR_AUTH; + break; + default: + return; + break; + } + + msn_session_set_error(cmdproc->session, msnerr, NULL); +} + +static void +ver_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnSession *session; + PurpleAccount *account; + gboolean protocol_supported = FALSE; + char proto_str[8]; + size_t i; + + session = cmdproc->session; + account = session->account; + + g_snprintf(proto_str, sizeof(proto_str), "MSNP%d", session->protocol_ver); + + for (i = 1; i < cmd->param_count; i++) + { + if (!strcmp(cmd->params[i], proto_str)) + { + protocol_supported = TRUE; + break; + } + } + + if (!protocol_supported) + { + msn_session_set_error(session, MSN_ERROR_UNSUPPORTED_PROTOCOL, + NULL); + return; + } + + msn_cmdproc_send(cmdproc, "CVR", + "0x0409 winnt 5.1 i386 MSNMSGR 6.0.0602 MSMSGS %s", + purple_account_get_username(account)); +} + +/************************************************************************** + * Log out + **************************************************************************/ + +static void +out_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + if (!g_ascii_strcasecmp(cmd->params[0], "OTH")) + msn_session_set_error(cmdproc->session, MSN_ERROR_SIGN_OTHER, + NULL); + else if (!g_ascii_strcasecmp(cmd->params[0], "SSD")) + msn_session_set_error(cmdproc->session, MSN_ERROR_SERV_DOWN, NULL); +} + +void +msn_notification_close(MsnNotification *notification) +{ + g_return_if_fail(notification != NULL); + + if (!notification->in_use) + return; + + msn_cmdproc_send_quick(notification->cmdproc, "OUT", NULL, NULL); + + msn_notification_disconnect(notification); +} + +/************************************************************************** + * Messages + **************************************************************************/ + +static void +msg_cmd_post(MsnCmdProc *cmdproc, MsnCommand *cmd, char *payload, + size_t len) +{ + MsnMessage *msg; + + msg = msn_message_new_from_cmd(cmdproc->session, cmd); + + msn_message_parse_payload(msg, payload, len); +#ifdef MSN_DEBUG_NS + msn_message_show_readable(msg, "Notification", TRUE); +#endif + + msn_cmdproc_process_msg(cmdproc, msg); + + msn_message_destroy(msg); +} + +static void +msg_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + /* NOTE: cmd is not always cmdproc->last_cmd, sometimes cmd is a queued + * command and we are processing it */ + + if (cmd->payload == NULL) + { + cmdproc->last_cmd->payload_cb = msg_cmd_post; + cmdproc->servconn->payload_len = atoi(cmd->params[2]); + } + else + { + g_return_if_fail(cmd->payload_cb != NULL); + + cmd->payload_cb(cmdproc, cmd, cmd->payload, cmd->payload_len); + } +} + +/************************************************************************** + * Challenges + **************************************************************************/ + +static void +chl_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnTransaction *trans; + char buf[33]; + const char *challenge_resp; + PurpleCipher *cipher; + PurpleCipherContext *context; + guchar digest[16]; + int i; + + cipher = purple_ciphers_find_cipher("md5"); + context = purple_cipher_context_new(cipher, NULL); + + purple_cipher_context_append(context, (const guchar *)cmd->params[1], + strlen(cmd->params[1])); + + challenge_resp = "VT6PX?UQTM4WM%YR"; + + purple_cipher_context_append(context, (const guchar *)challenge_resp, + strlen(challenge_resp)); + purple_cipher_context_digest(context, sizeof(digest), digest, NULL); + purple_cipher_context_destroy(context); + + for (i = 0; i < 16; i++) + g_snprintf(buf + (i*2), 3, "%02x", digest[i]); + + trans = msn_transaction_new(cmdproc, "QRY", "%s 32", "PROD0038W!61ZTF9"); + + msn_transaction_set_payload(trans, buf, 32); + + msn_cmdproc_send_trans(cmdproc, trans); +} + +/************************************************************************** + * Buddy Lists + **************************************************************************/ + +static void +add_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnSession *session; + MsnUser *user; + const char *list; + const char *passport; + const char *friendly; + MsnListId list_id; + int group_id; + + list = cmd->params[1]; + passport = cmd->params[3]; + friendly = purple_url_decode(cmd->params[4]); + + session = cmdproc->session; + + user = msn_userlist_find_user(session->userlist, passport); + + if (user == NULL) + { + user = msn_user_new(session->userlist, passport, friendly); + msn_userlist_add_user(session->userlist, user); + } + else + msn_user_set_friendly_name(user, friendly); + + list_id = msn_get_list_id(list); + + if (cmd->param_count >= 6) + group_id = atoi(cmd->params[5]); + else + group_id = -1; + + msn_got_add_user(session, user, list_id, group_id); + msn_user_update(user); +} + +static void +add_error(MsnCmdProc *cmdproc, MsnTransaction *trans, int error) +{ + MsnSession *session; + PurpleAccount *account; + PurpleConnection *gc; + const char *list, *passport; + char *reason = NULL; + char *msg = NULL; + char **params; + + session = cmdproc->session; + account = session->account; + gc = purple_account_get_connection(account); + params = g_strsplit(trans->params, " ", 0); + + list = params[0]; + passport = params[1]; + + if (!strcmp(list, "FL")) + msg = g_strdup_printf(_("Unable to add user on %s (%s)"), + purple_account_get_username(account), + purple_account_get_protocol_name(account)); + else if (!strcmp(list, "BL")) + msg = g_strdup_printf(_("Unable to block user on %s (%s)"), + purple_account_get_username(account), + purple_account_get_protocol_name(account)); + else if (!strcmp(list, "AL")) + msg = g_strdup_printf(_("Unable to permit user on %s (%s)"), + purple_account_get_username(account), + purple_account_get_protocol_name(account)); + + if (!strcmp(list, "FL")) + { + if (error == 210) + { + reason = g_strdup_printf(_("%s could not be added because " + "your buddy list is full."), passport); + } + } + + if (reason == NULL) + { + if (error == 208) + { + reason = g_strdup_printf(_("%s is not a valid passport account."), + passport); + } + else if (error == 500) + { + reason = g_strdup(_("Service Temporarily Unavailable.")); + } + else + { + reason = g_strdup(_("Unknown error.")); + } + } + + if (msg != NULL) + { + purple_notify_error(gc, NULL, msg, reason); + g_free(msg); + } + + if (!strcmp(list, "FL")) + { + PurpleBuddy *buddy; + + buddy = purple_find_buddy(account, passport); + + if (buddy != NULL) + purple_blist_remove_buddy(buddy); + } + + g_free(reason); + + g_strfreev(params); +} + +static void +adg_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnSession *session; + gint group_id; + const char *group_name; + + session = cmdproc->session; + + group_id = atoi(cmd->params[3]); + + group_name = purple_url_decode(cmd->params[2]); + + msn_group_new(session->userlist, group_id, group_name); + + /* There is a user that must me moved to this group */ + if (cmd->trans->data) + { + /* msn_userlist_move_buddy(); */ + MsnUserList *userlist = cmdproc->session->userlist; + MsnMoveBuddy *data = cmd->trans->data; + + if (data->old_group_name != NULL) + { + msn_userlist_rem_buddy(userlist, data->who, MSN_LIST_FL, data->old_group_name); + g_free(data->old_group_name); + } + + msn_userlist_add_buddy(userlist, data->who, MSN_LIST_FL, group_name); + g_free(data->who); + + } +} + +static void +qng_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnSession *session; + static int count = 0; + const char *passport; + PurpleAccount *account; + + session = cmdproc->session; + account = session->account; + + if (session->passport_info.file == NULL) + return; + + passport = purple_normalize(account, purple_account_get_username(account)); + + if ((strstr(passport, "@hotmail.") != NULL) || + (strstr(passport, "@msn.com") != NULL)) + return; + + if (count++ < 26) + return; + + count = 0; + msn_cmdproc_send(cmdproc, "URL", "%s", "INBOX"); +} + + +static void +fln_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnSlpLink *slplink; + MsnUser *user; + + user = msn_userlist_find_user(cmdproc->session->userlist, cmd->params[0]); + + user->status = "offline"; + msn_user_update(user); + + slplink = msn_session_find_slplink(cmdproc->session, cmd->params[0]); + + if (slplink != NULL) + msn_slplink_destroy(slplink); + +} + +static void +iln_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnSession *session; + PurpleAccount *account; + PurpleConnection *gc; + MsnUser *user; + MsnObject *msnobj; + const char *state, *passport, *friendly; + + session = cmdproc->session; + account = session->account; + gc = purple_account_get_connection(account); + + state = cmd->params[1]; + passport = cmd->params[2]; + friendly = purple_url_decode(cmd->params[3]); + + user = msn_userlist_find_user(session->userlist, passport); + + serv_got_alias(gc, passport, friendly); + + msn_user_set_friendly_name(user, friendly); + + if (session->protocol_ver >= 9 && cmd->param_count == 6) + { + msnobj = msn_object_new_from_string(purple_url_decode(cmd->params[5])); + msn_user_set_object(user, msnobj); + } + + msn_user_set_state(user, state); + msn_user_update(user); +} + +static void +ipg_cmd_post(MsnCmdProc *cmdproc, MsnCommand *cmd, char *payload, size_t len) +{ +#if 0 + purple_debug_misc("msn", "Incoming Page: {%s}\n", payload); +#endif +} + +static void +ipg_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + cmdproc->servconn->payload_len = atoi(cmd->params[0]); + cmdproc->last_cmd->payload_cb = ipg_cmd_post; +} + +static void +nln_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnSession *session; + PurpleAccount *account; + PurpleConnection *gc; + MsnUser *user; + MsnObject *msnobj; + int clientid; + const char *state, *passport, *friendly, *old_friendly; + + session = cmdproc->session; + account = session->account; + gc = purple_account_get_connection(account); + + state = cmd->params[0]; + passport = cmd->params[1]; + friendly = purple_url_decode(cmd->params[2]); + + user = msn_userlist_find_user(session->userlist, passport); + + old_friendly = msn_user_get_friendly_name(user); + if (!old_friendly || (old_friendly && (!friendly || strcmp(old_friendly, friendly)))) + { + serv_got_alias(gc, passport, friendly); + msn_user_set_friendly_name(user, friendly); + } + + if (session->protocol_ver >= 9) + { + if (cmd->param_count == 5) + { + msnobj = + msn_object_new_from_string(purple_url_decode(cmd->params[4])); + msn_user_set_object(user, msnobj); + } + else + { + msn_user_set_object(user, NULL); + } + } + + clientid = atoi(cmd->params[3]); + user->mobile = (clientid & MSN_CLIENT_CAP_MSNMOBILE); + + msn_user_set_state(user, state); + msn_user_update(user); +} + +#if 0 +static void +chg_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + char *state = cmd->params[1]; + int state_id = 0; + + if (!strcmp(state, "NLN")) + state_id = MSN_ONLINE; + else if (!strcmp(state, "BSY")) + state_id = MSN_BUSY; + else if (!strcmp(state, "IDL")) + state_id = MSN_IDLE; + else if (!strcmp(state, "BRB")) + state_id = MSN_BRB; + else if (!strcmp(state, "AWY")) + state_id = MSN_AWAY; + else if (!strcmp(state, "PHN")) + state_id = MSN_PHONE; + else if (!strcmp(state, "LUN")) + state_id = MSN_LUNCH; + else if (!strcmp(state, "HDN")) + state_id = MSN_HIDDEN; + + cmdproc->session->state = state_id; +} +#endif + + +static void +not_cmd_post(MsnCmdProc *cmdproc, MsnCommand *cmd, char *payload, size_t len) +{ +#if 0 + MSN_SET_PARAMS("NOT %d\r\n%s", cmdproc->servconn->payload, payload); + purple_debug_misc("msn", "Notification: {%s}\n", payload); +#endif +} + +static void +not_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + cmdproc->servconn->payload_len = atoi(cmd->params[0]); + cmdproc->last_cmd->payload_cb = not_cmd_post; +} + +static void +rea_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + /* TODO: This might be for us too */ + + MsnSession *session; + PurpleConnection *gc; + const char *friendly; + + session = cmdproc->session; + gc = session->account->gc; + friendly = purple_url_decode(cmd->params[3]); + + purple_connection_set_display_name(gc, friendly); +} + +static void +prp_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnSession *session = cmdproc->session; + const char *type, *value; + + g_return_if_fail(cmd->param_count >= 3); + + type = cmd->params[2]; + + if (cmd->param_count == 4) + { + value = cmd->params[3]; + if (!strcmp(type, "PHH")) + msn_user_set_home_phone(session->user, purple_url_decode(value)); + else if (!strcmp(type, "PHW")) + msn_user_set_work_phone(session->user, purple_url_decode(value)); + else if (!strcmp(type, "PHM")) + msn_user_set_mobile_phone(session->user, purple_url_decode(value)); + } + else + { + if (!strcmp(type, "PHH")) + msn_user_set_home_phone(session->user, NULL); + else if (!strcmp(type, "PHW")) + msn_user_set_work_phone(session->user, NULL); + else if (!strcmp(type, "PHM")) + msn_user_set_mobile_phone(session->user, NULL); + } +} + +static void +reg_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnSession *session; + int group_id; + const char *group_name; + + session = cmdproc->session; + group_id = atoi(cmd->params[2]); + group_name = purple_url_decode(cmd->params[3]); + + msn_userlist_rename_group_id(session->userlist, group_id, group_name); +} + +static void +reg_error(MsnCmdProc *cmdproc, MsnTransaction *trans, int error) +{ + int group_id; + char **params; + + params = g_strsplit(trans->params, " ", 0); + + group_id = atoi(params[0]); + + group_error_helper(cmdproc->session, _("Unable to rename group"), group_id, error); + + g_strfreev(params); +} + +static void +rem_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnSession *session; + MsnUser *user; + const char *list; + const char *passport; + MsnListId list_id; + int group_id; + + session = cmdproc->session; + list = cmd->params[1]; + passport = cmd->params[3]; + user = msn_userlist_find_user(session->userlist, passport); + + g_return_if_fail(user != NULL); + + list_id = msn_get_list_id(list); + + if (cmd->param_count == 5) + group_id = atoi(cmd->params[4]); + else + group_id = -1; + + msn_got_rem_user(session, user, list_id, group_id); + msn_user_update(user); +} + +static void +rmg_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnSession *session; + int group_id; + + session = cmdproc->session; + group_id = atoi(cmd->params[2]); + + msn_userlist_remove_group_id(session->userlist, group_id); +} + +static void +rmg_error(MsnCmdProc *cmdproc, MsnTransaction *trans, int error) +{ + int group_id; + char **params; + + params = g_strsplit(trans->params, " ", 0); + + group_id = atoi(params[0]); + + group_error_helper(cmdproc->session, _("Unable to delete group"), group_id, error); + + g_strfreev(params); +} + +static void +syn_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnSession *session; + MsnSync *sync; + int total_users; + + session = cmdproc->session; + + if (cmd->param_count == 2) + { + /* + * This can happen if we sent a SYN with an up-to-date + * buddy list revision, but we send 0 to get a full list. + * So, error out. + */ + + msn_session_set_error(cmdproc->session, MSN_ERROR_BAD_BLIST, NULL); + return; + } + + total_users = atoi(cmd->params[2]); + + sync = msn_sync_new(session); + sync->total_users = total_users; + sync->old_cbs_table = cmdproc->cbs_table; + + session->sync = sync; + cmdproc->cbs_table = sync->cbs_table; +} + +/************************************************************************** + * Misc commands + **************************************************************************/ + +static void +url_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnSession *session; + PurpleAccount *account; + const char *rru; + const char *url; + PurpleCipher *cipher; + PurpleCipherContext *context; + guchar digest[16]; + FILE *fd; + char *buf; + char buf2[3]; + char sendbuf[64]; + int i; + + session = cmdproc->session; + account = session->account; + + rru = cmd->params[1]; + url = cmd->params[2]; + + buf = g_strdup_printf("%s%lu%s", + session->passport_info.mspauth ? session->passport_info.mspauth : "BOGUS", + time(NULL) - session->passport_info.sl, + purple_connection_get_password(account->gc)); + + cipher = purple_ciphers_find_cipher("md5"); + context = purple_cipher_context_new(cipher, NULL); + + purple_cipher_context_append(context, (const guchar *)buf, strlen(buf)); + purple_cipher_context_digest(context, sizeof(digest), digest, NULL); + purple_cipher_context_destroy(context); + + g_free(buf); + + memset(sendbuf, 0, sizeof(sendbuf)); + + for (i = 0; i < 16; i++) + { + g_snprintf(buf2, sizeof(buf2), "%02x", digest[i]); + strcat(sendbuf, buf2); + } + + if (session->passport_info.file != NULL) + { + g_unlink(session->passport_info.file); + g_free(session->passport_info.file); + } + + if ((fd = purple_mkstemp(&session->passport_info.file, FALSE)) == NULL) + { + purple_debug_error("msn", + "Error opening temp passport file: %s\n", + strerror(errno)); + } + else + { +#ifdef _WIN32 + fputs("<!-- saved from url=(0013)about:internet -->\n", fd); +#endif + fputs("<html>\n" + "<head>\n" + "<noscript>\n" + "<meta http-equiv=\"Refresh\" content=\"0; " + "url=http://www.hotmail.com\">\n" + "</noscript>\n" + "</head>\n\n", + fd); + + fprintf(fd, "<body onload=\"document.pform.submit(); \">\n"); + fprintf(fd, "<form name=\"pform\" action=\"%s\" method=\"POST\">\n\n", + url); + fprintf(fd, "<input type=\"hidden\" name=\"mode\" value=\"ttl\">\n"); + fprintf(fd, "<input type=\"hidden\" name=\"login\" value=\"%s\">\n", + purple_account_get_username(account)); + fprintf(fd, "<input type=\"hidden\" name=\"username\" value=\"%s\">\n", + purple_account_get_username(account)); + if (session->passport_info.sid != NULL) + fprintf(fd, "<input type=\"hidden\" name=\"sid\" value=\"%s\">\n", + session->passport_info.sid); + if (session->passport_info.kv != NULL) + fprintf(fd, "<input type=\"hidden\" name=\"kv\" value=\"%s\">\n", + session->passport_info.kv); + fprintf(fd, "<input type=\"hidden\" name=\"id\" value=\"2\">\n"); + fprintf(fd, "<input type=\"hidden\" name=\"sl\" value=\"%ld\">\n", + time(NULL) - session->passport_info.sl); + fprintf(fd, "<input type=\"hidden\" name=\"rru\" value=\"%s\">\n", + rru); + if (session->passport_info.mspauth != NULL) + fprintf(fd, "<input type=\"hidden\" name=\"auth\" value=\"%s\">\n", + session->passport_info.mspauth); + fprintf(fd, "<input type=\"hidden\" name=\"creds\" value=\"%s\">\n", + sendbuf); /* TODO Digest me (huh? -- ChipX86) */ + fprintf(fd, "<input type=\"hidden\" name=\"svc\" value=\"mail\">\n"); + fprintf(fd, "<input type=\"hidden\" name=\"js\" value=\"yes\">\n"); + fprintf(fd, "</form></body>\n"); + fprintf(fd, "</html>\n"); + + if (fclose(fd)) + { + purple_debug_error("msn", + "Error closing temp passport file: %s\n", + strerror(errno)); + + g_unlink(session->passport_info.file); + g_free(session->passport_info.file); + session->passport_info.file = NULL; + } +#ifdef _WIN32 + else + { + /* + * Renaming file with .html extension, so that the + * win32 open_url will work. + */ + char *tmp; + + if ((tmp = + g_strdup_printf("%s.html", + session->passport_info.file)) != NULL) + { + if (g_rename(session->passport_info.file, + tmp) == 0) + { + g_free(session->passport_info.file); + session->passport_info.file = tmp; + } + else + g_free(tmp); + } + } +#endif + } +} +/************************************************************************** + * Switchboards + **************************************************************************/ + +static void +rng_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnSession *session; + MsnSwitchBoard *swboard; + const char *session_id; + char *host; + int port; + + session = cmdproc->session; + session_id = cmd->params[0]; + + msn_parse_socket(cmd->params[1], &host, &port); + + if (session->http_method) + port = 80; + + swboard = msn_switchboard_new(session); + + msn_switchboard_set_invited(swboard, TRUE); + msn_switchboard_set_session_id(swboard, cmd->params[0]); + msn_switchboard_set_auth_key(swboard, cmd->params[3]); + swboard->im_user = g_strdup(cmd->params[4]); + /* msn_switchboard_add_user(swboard, cmd->params[4]); */ + + if (!msn_switchboard_connect(swboard, host, port)) + msn_switchboard_destroy(swboard); + + g_free(host); +} + +static void +xfr_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + char *host; + int port; + + if (strcmp(cmd->params[1], "SB") && strcmp(cmd->params[1], "NS")) + { + /* Maybe we can have a generic bad command error. */ + purple_debug_error("msn", "Bad XFR command (%s)\n", cmd->params[1]); + return; + } + + msn_parse_socket(cmd->params[2], &host, &port); + + if (!strcmp(cmd->params[1], "SB")) + { + purple_debug_error("msn", "This shouldn't be handled here.\n"); + } + else if (!strcmp(cmd->params[1], "NS")) + { + MsnSession *session; + + session = cmdproc->session; + + msn_session_set_login_step(session, MSN_LOGIN_STEP_TRANSFER); + + msn_notification_connect(session->notification, host, port); + } + + g_free(host); +} + +/************************************************************************** + * Message Types + **************************************************************************/ + +static void +profile_msg(MsnCmdProc *cmdproc, MsnMessage *msg) +{ + MsnSession *session; + const char *value; + + session = cmdproc->session; + + if (strcmp(msg->remote_user, "Hotmail")) + /* This isn't an official message. */ + return; + + if ((value = msn_message_get_attr(msg, "kv")) != NULL) + { + g_free(session->passport_info.kv); + session->passport_info.kv = g_strdup(value); + } + + if ((value = msn_message_get_attr(msg, "sid")) != NULL) + { + g_free(session->passport_info.sid); + session->passport_info.sid = g_strdup(value); + } + + if ((value = msn_message_get_attr(msg, "MSPAuth")) != NULL) + { + g_free(session->passport_info.mspauth); + session->passport_info.mspauth = g_strdup(value); + } + + if ((value = msn_message_get_attr(msg, "ClientIP")) != NULL) + { + g_free(session->passport_info.client_ip); + session->passport_info.client_ip = g_strdup(value); + } + + if ((value = msn_message_get_attr(msg, "ClientPort")) != NULL) + session->passport_info.client_port = ntohs(atoi(value)); + + if ((value = msn_message_get_attr(msg, "LoginTime")) != NULL) + session->passport_info.sl = atol(value); +} + +static void +initial_email_msg(MsnCmdProc *cmdproc, MsnMessage *msg) +{ + MsnSession *session; + PurpleConnection *gc; + GHashTable *table; + const char *unread; + + session = cmdproc->session; + gc = session->account->gc; + + if (strcmp(msg->remote_user, "Hotmail")) + /* This isn't an official message. */ + return; + + if (session->passport_info.file == NULL) + { + MsnTransaction *trans; + trans = msn_transaction_new(cmdproc, "URL", "%s", "INBOX"); + msn_transaction_queue_cmd(trans, msg->cmd); + + msn_cmdproc_send_trans(cmdproc, trans); + + return; + } + + if (!purple_account_get_check_mail(session->account)) + return; + + table = msn_message_get_hashtable_from_body(msg); + + unread = g_hash_table_lookup(table, "Inbox-Unread"); + + if (unread != NULL) + { + int count = atoi(unread); + + if (count > 0) + { + const char *passport; + const char *url; + + passport = msn_user_get_passport(session->user); + url = session->passport_info.file; + + purple_notify_emails(gc, atoi(unread), FALSE, NULL, NULL, + &passport, &url, NULL, NULL); + } + } + + g_hash_table_destroy(table); +} + +static void +email_msg(MsnCmdProc *cmdproc, MsnMessage *msg) +{ + MsnSession *session; + PurpleConnection *gc; + GHashTable *table; + char *from, *subject, *tmp; + + session = cmdproc->session; + gc = session->account->gc; + + if (strcmp(msg->remote_user, "Hotmail")) + /* This isn't an official message. */ + return; + + if (session->passport_info.file == NULL) + { + MsnTransaction *trans; + trans = msn_transaction_new(cmdproc, "URL", "%s", "INBOX"); + msn_transaction_queue_cmd(trans, msg->cmd); + + msn_cmdproc_send_trans(cmdproc, trans); + + return; + } + + if (!purple_account_get_check_mail(session->account)) + return; + + table = msn_message_get_hashtable_from_body(msg); + + from = subject = NULL; + + tmp = g_hash_table_lookup(table, "From"); + if (tmp != NULL) + from = purple_mime_decode_field(tmp); + + tmp = g_hash_table_lookup(table, "Subject"); + if (tmp != NULL) + subject = purple_mime_decode_field(tmp); + + purple_notify_email(gc, + (subject != NULL ? subject : ""), + (from != NULL ? from : ""), + msn_user_get_passport(session->user), + session->passport_info.file, NULL, NULL); + + g_free(from); + g_free(subject); + + g_hash_table_destroy(table); +} + +static void +system_msg(MsnCmdProc *cmdproc, MsnMessage *msg) +{ + GHashTable *table; + const char *type_s; + + if (strcmp(msg->remote_user, "Hotmail")) + /* This isn't an official message. */ + return; + + table = msn_message_get_hashtable_from_body(msg); + + if ((type_s = g_hash_table_lookup(table, "Type")) != NULL) + { + int type = atoi(type_s); + char buf[MSN_BUF_LEN]; + int minutes; + + switch (type) + { + case 1: + minutes = atoi(g_hash_table_lookup(table, "Arg1")); + g_snprintf(buf, sizeof(buf), dngettext(PACKAGE, + "The MSN server will shut down for maintenance " + "in %d minute. You will automatically be " + "signed out at that time. Please finish any " + "conversations in progress.\n\nAfter the " + "maintenance has been completed, you will be " + "able to successfully sign in.", + "The MSN server will shut down for maintenance " + "in %d minutes. You will automatically be " + "signed out at that time. Please finish any " + "conversations in progress.\n\nAfter the " + "maintenance has been completed, you will be " + "able to successfully sign in.", minutes), + minutes); + default: + break; + } + + if (*buf != '\0') + purple_notify_info(cmdproc->session->account->gc, NULL, buf, NULL); + } + + g_hash_table_destroy(table); +} + +void +msn_notification_add_buddy(MsnNotification *notification, const char *list, + const char *who, const char *store_name, + int group_id) +{ + MsnCmdProc *cmdproc; + cmdproc = notification->servconn->cmdproc; + + if (group_id < 0 && !strcmp(list, "FL")) + group_id = 0; + + if (group_id >= 0) + { + msn_cmdproc_send(cmdproc, "ADD", "%s %s %s %d", + list, who, store_name, group_id); + } + else + { + msn_cmdproc_send(cmdproc, "ADD", "%s %s %s", list, who, store_name); + } +} + +void +msn_notification_rem_buddy(MsnNotification *notification, const char *list, + const char *who, int group_id) +{ + MsnCmdProc *cmdproc; + cmdproc = notification->servconn->cmdproc; + + if (group_id >= 0) + { + msn_cmdproc_send(cmdproc, "REM", "%s %s %d", list, who, group_id); + } + else + { + msn_cmdproc_send(cmdproc, "REM", "%s %s", list, who); + } +} + +/************************************************************************** + * Init + **************************************************************************/ + +void +msn_notification_init(void) +{ + /* TODO: check prp, blp */ + + cbs_table = msn_table_new(); + + /* Synchronous */ + msn_table_add_cmd(cbs_table, "CHG", "CHG", NULL); + msn_table_add_cmd(cbs_table, "CHG", "ILN", iln_cmd); + msn_table_add_cmd(cbs_table, "ADD", "ADD", add_cmd); + msn_table_add_cmd(cbs_table, "ADD", "ILN", iln_cmd); + msn_table_add_cmd(cbs_table, "REM", "REM", rem_cmd); + msn_table_add_cmd(cbs_table, "USR", "USR", usr_cmd); + msn_table_add_cmd(cbs_table, "USR", "XFR", xfr_cmd); + msn_table_add_cmd(cbs_table, "SYN", "SYN", syn_cmd); + msn_table_add_cmd(cbs_table, "CVR", "CVR", cvr_cmd); + msn_table_add_cmd(cbs_table, "VER", "VER", ver_cmd); + msn_table_add_cmd(cbs_table, "REA", "REA", rea_cmd); + msn_table_add_cmd(cbs_table, "PRP", "PRP", prp_cmd); + /* msn_table_add_cmd(cbs_table, "BLP", "BLP", blp_cmd); */ + msn_table_add_cmd(cbs_table, "BLP", "BLP", NULL); + msn_table_add_cmd(cbs_table, "REG", "REG", reg_cmd); + msn_table_add_cmd(cbs_table, "ADG", "ADG", adg_cmd); + msn_table_add_cmd(cbs_table, "RMG", "RMG", rmg_cmd); + msn_table_add_cmd(cbs_table, "XFR", "XFR", xfr_cmd); + + /* Asynchronous */ + msn_table_add_cmd(cbs_table, NULL, "IPG", ipg_cmd); + msn_table_add_cmd(cbs_table, NULL, "MSG", msg_cmd); + msn_table_add_cmd(cbs_table, NULL, "NOT", not_cmd); + + msn_table_add_cmd(cbs_table, NULL, "CHL", chl_cmd); + msn_table_add_cmd(cbs_table, NULL, "REM", rem_cmd); + msn_table_add_cmd(cbs_table, NULL, "ADD", add_cmd); + + msn_table_add_cmd(cbs_table, NULL, "QRY", NULL); + msn_table_add_cmd(cbs_table, NULL, "QNG", qng_cmd); + msn_table_add_cmd(cbs_table, NULL, "FLN", fln_cmd); + msn_table_add_cmd(cbs_table, NULL, "NLN", nln_cmd); + msn_table_add_cmd(cbs_table, NULL, "ILN", iln_cmd); + msn_table_add_cmd(cbs_table, NULL, "OUT", out_cmd); + msn_table_add_cmd(cbs_table, NULL, "RNG", rng_cmd); + + msn_table_add_cmd(cbs_table, NULL, "URL", url_cmd); + + msn_table_add_cmd(cbs_table, "fallback", "XFR", xfr_cmd); + + msn_table_add_error(cbs_table, "ADD", add_error); + msn_table_add_error(cbs_table, "REG", reg_error); + msn_table_add_error(cbs_table, "RMG", rmg_error); + /* msn_table_add_error(cbs_table, "REA", rea_error); */ + msn_table_add_error(cbs_table, "USR", usr_error); + + msn_table_add_msg_type(cbs_table, + "text/x-msmsgsprofile", + profile_msg); + msn_table_add_msg_type(cbs_table, + "text/x-msmsgsinitialemailnotification", + initial_email_msg); + msn_table_add_msg_type(cbs_table, + "text/x-msmsgsemailnotification", + email_msg); + msn_table_add_msg_type(cbs_table, + "application/x-msmsgssystemmessage", + system_msg); +} + +void +msn_notification_end(void) +{ + msn_table_destroy(cbs_table); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/notification.h Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,70 @@ +/** + * @file notification.h Notification server functions + * + * 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 _MSN_NOTIFICATION_H_ +#define _MSN_NOTIFICATION_H_ + +typedef struct _MsnNotification MsnNotification; + +#include "session.h" +#include "servconn.h" +#include "cmdproc.h" + +struct _MsnNotification +{ + MsnSession *session; + MsnCmdProc *cmdproc; + MsnServConn *servconn; + + gboolean in_use; +}; + +#include "state.h" + +void msn_notification_end(void); +void msn_notification_init(void); + +void msn_notification_add_buddy(MsnNotification *notification, + const char *list, const char *who, + const char *store_name, int group_id); +void msn_notification_rem_buddy(MsnNotification *notification, + const char *list, const char *who, + int group_id); +MsnNotification *msn_notification_new(MsnSession *session); +void msn_notification_destroy(MsnNotification *notification); +gboolean msn_notification_connect(MsnNotification *notification, + const char *host, int port); +void msn_notification_disconnect(MsnNotification *notification); + +/** + * Closes a notification. + * + * It's first closed, and then disconnected. + * + * @param notification The notification object to close. + */ +void msn_notification_close(MsnNotification *notification); + +void msn_got_login_params(MsnSession *session, const char *login_params); + +#endif /* _MSN_NOTIFICATION_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/object.c Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,345 @@ +/** + * @file object.c MSNObject API + * + * 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 "object.h" +#include "debug.h" + +#define GET_STRING_TAG(field, id) \ + if ((tag = strstr(str, id "=\"")) != NULL) \ + { \ + tag += strlen(id "=\""); \ + c = strchr(tag, '"'); \ + if (c != NULL) \ + { \ + if (obj->field != NULL) \ + g_free(obj->field); \ + obj->field = g_strndup(tag, c - tag); \ + } \ + } + +#define GET_INT_TAG(field, id) \ + if ((tag = strstr(str, id "=\"")) != NULL) \ + { \ + char buf[16]; \ + size_t offset; \ + tag += strlen(id "=\""); \ + c = strchr(tag, '"'); \ + if (c != NULL) \ + { \ + memset(buf, 0, sizeof(buf)); \ + offset = c - tag; \ + if (offset >= sizeof(buf)) \ + offset = sizeof(buf) - 1; \ + strncpy(buf, tag, offset); \ + obj->field = atoi(buf); \ + } \ + } + +static GList *local_objs; + +MsnObject * +msn_object_new(void) +{ + MsnObject *obj; + + obj = g_new0(MsnObject, 1); + + msn_object_set_type(obj, MSN_OBJECT_UNKNOWN); + msn_object_set_friendly(obj, "AAA="); + + return obj; +} + +MsnObject * +msn_object_new_from_string(const char *str) +{ + MsnObject *obj; + char *tag, *c; + + g_return_val_if_fail(str != NULL, NULL); + + if (strncmp(str, "<msnobj ", 8)) + return NULL; + + obj = msn_object_new(); + + GET_STRING_TAG(creator, "Creator"); + GET_INT_TAG(size, "Size"); + GET_INT_TAG(type, "Type"); + GET_STRING_TAG(location, "Location"); + GET_STRING_TAG(friendly, "Friendly"); + GET_STRING_TAG(sha1d, "SHA1D"); + GET_STRING_TAG(sha1c, "SHA1C"); + + /* If we are missing any of the required elements then discard the object */ + /* SHA1C is not always sent anymore */ + if (obj->creator == NULL || obj->size == 0 || obj->type == 0 + || obj->location == NULL || obj->friendly == NULL + || obj->sha1d == NULL /*|| obj->sha1c == NULL*/) { + purple_debug_error("msn", "Discarding invalid msnobj: '%s'\n", str); + msn_object_destroy(obj); + obj = NULL; + } + + return obj; +} + +void +msn_object_destroy(MsnObject *obj) +{ + g_return_if_fail(obj != NULL); + + g_free(obj->creator); + g_free(obj->location); + g_free(obj->friendly); + g_free(obj->sha1d); + g_free(obj->sha1c); + + purple_imgstore_unref(obj->img); + + if (obj->local) + local_objs = g_list_remove(local_objs, obj); + + g_free(obj); +} + +char * +msn_object_to_string(const MsnObject *obj) +{ + char *str; + const char *sha1c; + + g_return_val_if_fail(obj != NULL, NULL); + + sha1c = msn_object_get_sha1c(obj); + + str = g_strdup_printf("<msnobj Creator=\"%s\" Size=\"%d\" Type=\"%d\" " + "Location=\"%s\" Friendly=\"%s\" SHA1D=\"%s\"" + "%s%s%s/>", + msn_object_get_creator(obj), + msn_object_get_size(obj), + msn_object_get_type(obj), + msn_object_get_location(obj), + msn_object_get_friendly(obj), + msn_object_get_sha1d(obj), + sha1c ? " SHA1C=\"" : "", + sha1c ? sha1c : "", + sha1c ? "\"" : ""); + + return str; +} + +void +msn_object_set_creator(MsnObject *obj, const char *creator) +{ + g_return_if_fail(obj != NULL); + + if (obj->creator != NULL) + g_free(obj->creator); + + obj->creator = (creator == NULL ? NULL : g_strdup(creator)); +} + +void +msn_object_set_size(MsnObject *obj, int size) +{ + g_return_if_fail(obj != NULL); + + obj->size = size; +} + +void +msn_object_set_type(MsnObject *obj, MsnObjectType type) +{ + g_return_if_fail(obj != NULL); + + obj->type = type; +} + +void +msn_object_set_location(MsnObject *obj, const char *location) +{ + g_return_if_fail(obj != NULL); + + if (obj->location != NULL) + g_free(obj->location); + + obj->location = (location == NULL ? NULL : g_strdup(location)); +} + +void +msn_object_set_friendly(MsnObject *obj, const char *friendly) +{ + g_return_if_fail(obj != NULL); + + if (obj->friendly != NULL) + g_free(obj->friendly); + + obj->friendly = (friendly == NULL ? NULL : g_strdup(friendly)); +} + +void +msn_object_set_sha1d(MsnObject *obj, const char *sha1d) +{ + g_return_if_fail(obj != NULL); + + if (obj->sha1d != NULL) + g_free(obj->sha1d); + + obj->sha1d = (sha1d == NULL ? NULL : g_strdup(sha1d)); +} + +void +msn_object_set_sha1c(MsnObject *obj, const char *sha1c) +{ + g_return_if_fail(obj != NULL); + + if (obj->sha1c != NULL) + g_free(obj->sha1c); + + obj->sha1c = (sha1c == NULL ? NULL : g_strdup(sha1c)); +} + +const char * +msn_object_get_creator(const MsnObject *obj) +{ + g_return_val_if_fail(obj != NULL, NULL); + + return obj->creator; +} + +int +msn_object_get_size(const MsnObject *obj) +{ + g_return_val_if_fail(obj != NULL, 0); + + return obj->size; +} + +MsnObjectType +msn_object_get_type(const MsnObject *obj) +{ + g_return_val_if_fail(obj != NULL, MSN_OBJECT_UNKNOWN); + + return obj->type; +} + +const char * +msn_object_get_location(const MsnObject *obj) +{ + g_return_val_if_fail(obj != NULL, NULL); + + return obj->location; +} + +const char * +msn_object_get_friendly(const MsnObject *obj) +{ + g_return_val_if_fail(obj != NULL, NULL); + + return obj->friendly; +} + +const char * +msn_object_get_sha1d(const MsnObject *obj) +{ + g_return_val_if_fail(obj != NULL, NULL); + + return obj->sha1d; +} + +const char * +msn_object_get_sha1c(const MsnObject *obj) +{ + g_return_val_if_fail(obj != NULL, NULL); + + return obj->sha1c; +} + +const char * +msn_object_get_sha1(const MsnObject *obj) +{ + g_return_val_if_fail(obj != NULL, NULL); + + if(obj->sha1c != NULL) { + return obj->sha1c; + } else { + return obj->sha1d; + } +} + +static MsnObject * +msn_object_find_local(const char *sha1) +{ + GList *l; + + g_return_val_if_fail(sha1 != NULL, NULL); + + for (l = local_objs; l != NULL; l = l->next) + { + MsnObject *local_obj = l->data; + + if (!strcmp(msn_object_get_sha1(local_obj), sha1)) + return local_obj; + } + + return NULL; + +} + +void +msn_object_set_local(MsnObject *obj) +{ + g_return_if_fail(obj != NULL); + + obj->local = TRUE; + + local_objs = g_list_append(local_objs, obj); +} + +void +msn_object_set_image(MsnObject *obj, PurpleStoredImage *img) +{ + g_return_if_fail(obj != NULL); + g_return_if_fail(img != NULL); + + /* obj->local = TRUE; */ + + purple_imgstore_unref(obj->img); + obj->img = purple_imgstore_ref(img); +} + +PurpleStoredImage * +msn_object_get_image(const MsnObject *obj) +{ + MsnObject *local_obj; + + g_return_val_if_fail(obj != NULL, NULL); + + local_obj = msn_object_find_local(msn_object_get_sha1(obj)); + + if (local_obj != NULL) + return local_obj->img; + + return NULL; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/object.h Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,229 @@ +/** + * @file object.h MSNObject API + * + * 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 _MSN_OBJECT_H_ +#define _MSN_OBJECT_H_ + +#include "imgstore.h" + +#include "internal.h" + +typedef enum +{ + MSN_OBJECT_UNKNOWN = -1, /**< Unknown object */ + MSN_OBJECT_RESERVED1 = 1, /**< Reserved */ + MSN_OBJECT_EMOTICON = 2, /**< Custom Emoticon */ + MSN_OBJECT_USERTILE = 3, /**< UserTile (buddy icon) */ + MSN_OBJECT_RESERVED2 = 4, /**< Reserved */ + MSN_OBJECT_BACKGROUND = 5 /**< Background */ + +} MsnObjectType; + +typedef struct +{ + gboolean local; + + char *creator; + int size; + MsnObjectType type; + PurpleStoredImage *img; + char *location; + char *friendly; + char *sha1d; + char *sha1c; + +} MsnObject; + +/** + * Creates a MsnObject structure. + * + * @return A new MsnObject structure. + */ +MsnObject *msn_object_new(void); + +/** + * Creates a MsnObject structure from a string. + * + * @param str The string. + * + * @return The new MsnObject structure. + */ +MsnObject *msn_object_new_from_string(const char *str); + +/** + * Destroys an MsnObject structure. + * + * @param obj The object structure. + */ +void msn_object_destroy(MsnObject *obj); + +/** + * Outputs a string representation of an MsnObject. + * + * @param obj The object. + * + * @return The string representation. This must be freed. + */ +char *msn_object_to_string(const MsnObject *obj); + +/** + * Sets the creator field in a MsnObject. + * + * @param creator The creator value. + */ +void msn_object_set_creator(MsnObject *obj, const char *creator); + +/** + * Sets the size field in a MsnObject. + * + * @param size The size value. + */ +void msn_object_set_size(MsnObject *obj, int size); + +/** + * Sets the type field in a MsnObject. + * + * @param type The type value. + */ +void msn_object_set_type(MsnObject *obj, MsnObjectType type); + +/** + * Sets the location field in a MsnObject. + * + * @param location The location value. + */ +void msn_object_set_location(MsnObject *obj, const char *location); + +/** + * Sets the friendly name field in a MsnObject. + * + * @param friendly The friendly name value. + */ +void msn_object_set_friendly(MsnObject *obj, const char *friendly); + +/** + * Sets the SHA1D field in a MsnObject. + * + * @param sha1d The sha1d value. + */ +void msn_object_set_sha1d(MsnObject *obj, const char *sha1d); + +/** + * Sets the SHA1C field in a MsnObject. + * + * @param sha1c The sha1c value. + */ +void msn_object_set_sha1c(MsnObject *obj, const char *sha1c); + +/** + * Associates an image with a MsnObject. + * + * @param obj The object. + * @param img The image to associate. + */ +void msn_object_set_image(MsnObject *obj, PurpleStoredImage *img); + +/** + * Returns a MsnObject's creator value. + * + * @param obj The object. + * + * @return The creator value. + */ +const char *msn_object_get_creator(const MsnObject *obj); + +/** + * Returns a MsnObject's size value. + * + * @param obj The object. + * + * @return The size value. + */ +int msn_object_get_size(const MsnObject *obj); + +/** + * Returns a MsnObject's type. + * + * @param obj The object. + * + * @return The object type. + */ +MsnObjectType msn_object_get_type(const MsnObject *obj); + +/** + * Returns a MsnObject's location value. + * + * @param obj The object. + * + * @return The location value. + */ +const char *msn_object_get_location(const MsnObject *obj); + +/** + * Returns a MsnObject's friendly name value. + * + * @param obj The object. + * + * @return The friendly name value. + */ +const char *msn_object_get_friendly(const MsnObject *obj); + +/** + * Returns a MsnObject's SHA1D value. + * + * @param obj The object. + * + * @return The SHA1D value. + */ +const char *msn_object_get_sha1d(const MsnObject *obj); + +/** + * Returns a MsnObject's SHA1C value. + * + * @param obj The object. + * + * @return The SHA1C value. + */ +const char *msn_object_get_sha1c(const MsnObject *obj); + +/** + * Returns a MsnObject's SHA1C value if it exists, otherwise SHA1D. + * + * @param obj The object. + * + * @return The SHA1C value. + */ +const char *msn_object_get_sha1(const MsnObject *obj); + +/** + * Returns the image associated with the MsnObject. + * + * @param obj The object. + * + * @return The associated image. + */ +PurpleStoredImage *msn_object_get_image(const MsnObject *obj); + +void msn_object_set_local(MsnObject *obj); + +#endif /* _MSN_OBJECT_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/page.c Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,89 @@ +/** + * @file page.c Paging functions + * + * 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 "msn.h" +#include "page.h" + +MsnPage * +msn_page_new(void) +{ + MsnPage *page; + + page = g_new0(MsnPage, 1); + + return page; +} + +void +msn_page_destroy(MsnPage *page) +{ + g_return_if_fail(page != NULL); + + if (page->body != NULL) + g_free(page->body); + + if (page->from_location != NULL) + g_free(page->from_location); + + if (page->from_phone != NULL) + g_free(page->from_phone); + + g_free(page); +} + +char * +msn_page_gen_payload(const MsnPage *page, size_t *ret_size) +{ + char *str; + + g_return_val_if_fail(page != NULL, NULL); + + str = + g_strdup_printf("<TEXT xml:space=\"preserve\" enc=\"utf-8\">%s</TEXT>", + msn_page_get_body(page)); + + if (ret_size != NULL) + *ret_size = strlen(str); + + return str; +} + +void +msn_page_set_body(MsnPage *page, const char *body) +{ + g_return_if_fail(page != NULL); + g_return_if_fail(body != NULL); + + if (page->body != NULL) + g_free(page->body); + + page->body = g_strdup(body); +} + +const char * +msn_page_get_body(const MsnPage *page) +{ + g_return_val_if_fail(page != NULL, NULL); + + return page->body; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/page.h Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,81 @@ +/** + * @file page.h Paging functions + * + * 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 _MSN_PAGE_H_ +#define _MSN_PAGE_H_ + +typedef struct _MsnPage MsnPage; + +#include "session.h" + +/** + * A page. + */ +struct _MsnPage +{ + char *from_location; + char *from_phone; + + char *body; +}; + +/** + * Creates a new, empty page. + * + * @return A new page. + */ +MsnPage *msn_page_new(void); + +/** + * Destroys a page. + */ +void msn_page_destroy(MsnPage *page); + +/** + * Generates the payload data of a page. + * + * @param page The page. + * @param ret_size The returned size of the payload. + * + * @return The payload data of a page. + */ +char *msn_page_gen_payload(const MsnPage *page, size_t *ret_size); + +/** + * Sets the body of a page. + * + * @param page The page. + * @param body The body of the page. + */ +void msn_page_set_body(MsnPage *page, const char *body); + +/** + * Returns the body of the page. + * + * @param page The page. + * + * @return The body of the page. + */ +const char *msn_page_get_body(const MsnPage *page); + +#endif /* _MSN_PAGE_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/servconn.c Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,550 @@ +/** + * @file servconn.c Server connection functions + * + * 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 "msn.h" +#include "servconn.h" +#include "error.h" + +static void read_cb(gpointer data, gint source, PurpleInputCondition cond); + +/************************************************************************** + * Main + **************************************************************************/ + +MsnServConn * +msn_servconn_new(MsnSession *session, MsnServConnType type) +{ + MsnServConn *servconn; + + g_return_val_if_fail(session != NULL, NULL); + + servconn = g_new0(MsnServConn, 1); + + servconn->type = type; + + servconn->session = session; + servconn->cmdproc = msn_cmdproc_new(session); + servconn->cmdproc->servconn = servconn; + + servconn->httpconn = msn_httpconn_new(servconn); + + servconn->num = session->servconns_count++; + + servconn->tx_buf = purple_circ_buffer_new(MSN_BUF_LEN); + servconn->tx_handler = 0; + + return servconn; +} + +void +msn_servconn_destroy(MsnServConn *servconn) +{ + g_return_if_fail(servconn != NULL); + + if (servconn->processing) + { + servconn->wasted = TRUE; + return; + } + + if (servconn->connected) + msn_servconn_disconnect(servconn); + + if (servconn->destroy_cb) + servconn->destroy_cb(servconn); + + if (servconn->httpconn != NULL) + msn_httpconn_destroy(servconn->httpconn); + + g_free(servconn->host); + + purple_circ_buffer_destroy(servconn->tx_buf); + if (servconn->tx_handler > 0) + purple_input_remove(servconn->tx_handler); + + msn_cmdproc_destroy(servconn->cmdproc); + g_free(servconn); +} + +void +msn_servconn_set_connect_cb(MsnServConn *servconn, + void (*connect_cb)(MsnServConn *)) +{ + g_return_if_fail(servconn != NULL); + servconn->connect_cb = connect_cb; +} + +void +msn_servconn_set_disconnect_cb(MsnServConn *servconn, + void (*disconnect_cb)(MsnServConn *)) +{ + g_return_if_fail(servconn != NULL); + + servconn->disconnect_cb = disconnect_cb; +} + +void +msn_servconn_set_destroy_cb(MsnServConn *servconn, + void (*destroy_cb)(MsnServConn *)) +{ + g_return_if_fail(servconn != NULL); + + servconn->destroy_cb = destroy_cb; +} + +/************************************************************************** + * Utility + **************************************************************************/ + +void +msn_servconn_got_error(MsnServConn *servconn, MsnServConnError error) +{ + char *tmp; + const char *reason; + + const char *names[] = { "Notification", "Switchboard" }; + const char *name; + + name = names[servconn->type]; + + switch (error) + { + case MSN_SERVCONN_ERROR_CONNECT: + reason = _("Unable to connect"); break; + case MSN_SERVCONN_ERROR_WRITE: + reason = _("Writing error"); break; + case MSN_SERVCONN_ERROR_READ: + reason = _("Reading error"); break; + default: + reason = _("Unknown error"); break; + } + + purple_debug_error("msn", "Connection error from %s server (%s): %s\n", + name, servconn->host, reason); + tmp = g_strdup_printf(_("Connection error from %s server:\n%s"), + name, reason); + + if (servconn->type == MSN_SERVCONN_NS) + { + msn_session_set_error(servconn->session, MSN_ERROR_SERVCONN, tmp); + } + else if (servconn->type == MSN_SERVCONN_SB) + { + MsnSwitchBoard *swboard; + swboard = servconn->cmdproc->data; + if (swboard != NULL) + swboard->error = MSN_SB_ERROR_CONNECTION; + } + + msn_servconn_disconnect(servconn); + + g_free(tmp); +} + +/************************************************************************** + * Connect + **************************************************************************/ + +static void +connect_cb(gpointer data, gint source, const gchar *error_message) +{ + MsnServConn *servconn; + + servconn = data; + servconn->connect_data = NULL; + servconn->processing = FALSE; + + if (servconn->wasted) + { + if (source >= 0) + close(source); + msn_servconn_destroy(servconn); + return; + } + + servconn->fd = source; + + if (source >= 0) + { + servconn->connected = TRUE; + + /* Someone wants to know we connected. */ + servconn->connect_cb(servconn); + servconn->inpa = purple_input_add(servconn->fd, PURPLE_INPUT_READ, + read_cb, data); + } + else + { + purple_debug_error("msn", "Connection error: %s\n", error_message); + msn_servconn_got_error(servconn, MSN_SERVCONN_ERROR_CONNECT); + } +} + +gboolean +msn_servconn_connect(MsnServConn *servconn, const char *host, int port) +{ + MsnSession *session; + + g_return_val_if_fail(servconn != NULL, FALSE); + g_return_val_if_fail(host != NULL, FALSE); + g_return_val_if_fail(port > 0, FALSE); + + session = servconn->session; + + if (servconn->connected) + msn_servconn_disconnect(servconn); + + g_free(servconn->host); + servconn->host = g_strdup(host); + + if (session->http_method) + { + /* HTTP Connection. */ + + if (!servconn->httpconn->connected) + if (!msn_httpconn_connect(servconn->httpconn, host, port)) + return FALSE; + + servconn->connected = TRUE; + servconn->httpconn->virgin = TRUE; + + /* Someone wants to know we connected. */ + servconn->connect_cb(servconn); + + return TRUE; + } + + servconn->connect_data = purple_proxy_connect(NULL, session->account, + host, port, connect_cb, servconn); + + if (servconn->connect_data != NULL) + { + servconn->processing = TRUE; + return TRUE; + } + else + return FALSE; +} + +void +msn_servconn_disconnect(MsnServConn *servconn) +{ + g_return_if_fail(servconn != NULL); + + if (!servconn->connected) + { + /* We could not connect. */ + if (servconn->disconnect_cb != NULL) + servconn->disconnect_cb(servconn); + + return; + } + + if (servconn->session->http_method) + { + /* Fake disconnection. */ + if (servconn->disconnect_cb != NULL) + servconn->disconnect_cb(servconn); + + return; + } + + if (servconn->connect_data != NULL) + { + purple_proxy_connect_cancel(servconn->connect_data); + servconn->connect_data = NULL; + } + + if (servconn->inpa > 0) + { + purple_input_remove(servconn->inpa); + servconn->inpa = 0; + } + + close(servconn->fd); + + servconn->rx_buf = NULL; + servconn->rx_len = 0; + servconn->payload_len = 0; + + servconn->connected = FALSE; + + if (servconn->disconnect_cb != NULL) + servconn->disconnect_cb(servconn); +} + +static void +servconn_write_cb(gpointer data, gint source, PurpleInputCondition cond) +{ + MsnServConn *servconn = data; + int ret, writelen; + + writelen = purple_circ_buffer_get_max_read(servconn->tx_buf); + + if (writelen == 0) { + purple_input_remove(servconn->tx_handler); + servconn->tx_handler = 0; + return; + } + + ret = write(servconn->fd, servconn->tx_buf->outptr, writelen); + + if (ret < 0 && errno == EAGAIN) + return; + else if (ret <= 0) { + msn_servconn_got_error(servconn, MSN_SERVCONN_ERROR_WRITE); + return; + } + + purple_circ_buffer_mark_read(servconn->tx_buf, ret); +} + +ssize_t +msn_servconn_write(MsnServConn *servconn, const char *buf, size_t len) +{ + ssize_t ret = 0; + + g_return_val_if_fail(servconn != NULL, 0); + + if (!servconn->session->http_method) + { + if (servconn->tx_handler == 0) { + switch (servconn->type) + { + case MSN_SERVCONN_NS: + case MSN_SERVCONN_SB: + ret = write(servconn->fd, buf, len); + break; +#if 0 + case MSN_SERVCONN_DC: + ret = write(servconn->fd, &buf, sizeof(len)); + ret = write(servconn->fd, buf, len); + break; +#endif + default: + ret = write(servconn->fd, buf, len); + break; + } + } else { + ret = -1; + errno = EAGAIN; + } + + if (ret < 0 && errno == EAGAIN) + ret = 0; + if (ret >= 0 && ret < len) { + if (servconn->tx_handler == 0) + servconn->tx_handler = purple_input_add( + servconn->fd, PURPLE_INPUT_WRITE, + servconn_write_cb, servconn); + purple_circ_buffer_append(servconn->tx_buf, buf + ret, + len - ret); + } + } + else + { + ret = msn_httpconn_write(servconn->httpconn, buf, len); + } + + if (ret == -1) + { + msn_servconn_got_error(servconn, MSN_SERVCONN_ERROR_WRITE); + } + + return ret; +} + +static void +read_cb(gpointer data, gint source, PurpleInputCondition cond) +{ + MsnServConn *servconn; + MsnSession *session; + char buf[MSN_BUF_LEN]; + char *cur, *end, *old_rx_buf; + int len, cur_len; + + servconn = data; + session = servconn->session; + + len = read(servconn->fd, buf, sizeof(buf) - 1); + + if (len < 0 && errno == EAGAIN) + return; + else if (len <= 0) + { + purple_debug_error("msn", "servconn read error, len: %d error: %s\n", len, strerror(errno)); + msn_servconn_got_error(servconn, MSN_SERVCONN_ERROR_READ); + + return; + } + + buf[len] = '\0'; + + servconn->rx_buf = g_realloc(servconn->rx_buf, len + servconn->rx_len + 1); + memcpy(servconn->rx_buf + servconn->rx_len, buf, len + 1); + servconn->rx_len += len; + + end = old_rx_buf = servconn->rx_buf; + + servconn->processing = TRUE; + + do + { + cur = end; + + if (servconn->payload_len) + { + if (servconn->payload_len > servconn->rx_len) + /* The payload is still not complete. */ + break; + + cur_len = servconn->payload_len; + end += cur_len; + } + else + { + end = strstr(cur, "\r\n"); + + if (end == NULL) + /* The command is still not complete. */ + break; + + *end = '\0'; + end += 2; + cur_len = end - cur; + } + + servconn->rx_len -= cur_len; + + if (servconn->payload_len) + { + msn_cmdproc_process_payload(servconn->cmdproc, cur, cur_len); + servconn->payload_len = 0; + } + else + { + msn_cmdproc_process_cmd_text(servconn->cmdproc, cur); + } + } while (servconn->connected && !servconn->wasted && servconn->rx_len > 0); + + if (servconn->connected && !servconn->wasted) + { + if (servconn->rx_len > 0) + servconn->rx_buf = g_memdup(cur, servconn->rx_len); + else + servconn->rx_buf = NULL; + } + + servconn->processing = FALSE; + + if (servconn->wasted) + msn_servconn_destroy(servconn); + + g_free(old_rx_buf); +} + +#if 0 +static int +create_listener(int port) +{ + int fd; + const int on = 1; + +#if 0 + struct addrinfo hints; + struct addrinfo *c, *res; + char port_str[5]; + + snprintf(port_str, sizeof(port_str), "%d", port); + + memset(&hints, 0, sizeof(hints)); + + hints.ai_flags = AI_PASSIVE; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + if (getaddrinfo(NULL, port_str, &hints, &res) != 0) + { + purple_debug_error("msn", "Could not get address info: %s.\n", + port_str); + return -1; + } + + for (c = res; c != NULL; c = c->ai_next) + { + fd = socket(c->ai_family, c->ai_socktype, c->ai_protocol); + + if (fd < 0) + continue; + + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + + if (bind(fd, c->ai_addr, c->ai_addrlen) == 0) + break; + + close(fd); + } + + if (c == NULL) + { + purple_debug_error("msn", "Could not find socket: %s.\n", port_str); + return -1; + } + + freeaddrinfo(res); +#else + struct sockaddr_in sockin; + + fd = socket(AF_INET, SOCK_STREAM, 0); + + if (fd < 0) + return -1; + + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) != 0) + { + close(fd); + return -1; + } + + memset(&sockin, 0, sizeof(struct sockaddr_in)); + sockin.sin_family = AF_INET; + sockin.sin_port = htons(port); + + if (bind(fd, (struct sockaddr *)&sockin, sizeof(struct sockaddr_in)) != 0) + { + close(fd); + return -1; + } +#endif + + if (listen (fd, 4) != 0) + { + close (fd); + return -1; + } + + fcntl(fd, F_SETFL, O_NONBLOCK); + + return fd; +} +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/servconn.h Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,171 @@ +/** + * @file servconn.h Server connection functions + * + * 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 _MSN_SERVCONN_H_ +#define _MSN_SERVCONN_H_ + +typedef struct _MsnServConn MsnServConn; + +#include "session.h" +#include "cmdproc.h" + +#include "proxy.h" +#include "httpconn.h" + +/** + * Connection error types. + */ +typedef enum +{ + MSN_SERVCONN_ERROR_NONE, + MSN_SERVCONN_ERROR_CONNECT, + MSN_SERVCONN_ERROR_WRITE, + MSN_SERVCONN_ERROR_READ, + +} MsnServConnError; + +/** + * Connection types. + */ +typedef enum +{ + MSN_SERVCONN_NS, + MSN_SERVCONN_SB + +} MsnServConnType; + +/** + * A Connection. + */ +struct _MsnServConn +{ + MsnServConnType type; /**< The type of this connection. */ + MsnSession *session; /**< The MSN session of this connection. */ + MsnCmdProc *cmdproc; /**< The command processor of this connection. */ + + PurpleProxyConnectData *connect_data; + + gboolean connected; /**< A flag that states if it's connected. */ + gboolean processing; /**< A flag that states if something is working + with this connection. */ + gboolean wasted; /**< A flag that states if it should be destroyed. */ + + char *host; /**< The host this connection is connected or should be + connected to. */ + int num; /**< A number id of this connection. */ + + MsnHttpConn *httpconn; /**< The HTTP connection this connection should use. */ + + int fd; /**< The connection's file descriptor. */ + int inpa; /**< The connection's input handler. */ + + char *rx_buf; /**< The receive buffer. */ + int rx_len; /**< The receive buffer lenght. */ + + size_t payload_len; /**< The length of the payload. + It's only set when we've received a command that + has a payload. */ + + PurpleCircBuffer *tx_buf; + guint tx_handler; + + void (*connect_cb)(MsnServConn *); /**< The callback to call when connecting. */ + void (*disconnect_cb)(MsnServConn *); /**< The callback to call when disconnecting. */ + void (*destroy_cb)(MsnServConn *); /**< The callback to call when destroying. */ +}; + +/** + * Creates a new connection object. + * + * @param session The session. + * @param type The type of the connection. + */ +MsnServConn *msn_servconn_new(MsnSession *session, MsnServConnType type); + +/** + * Destroys a connection object. + * + * @param servconn The connection. + */ +void msn_servconn_destroy(MsnServConn *servconn); + +/** + * Connects to a host. + * + * @param servconn The connection. + * @param host The host. + * @param port The port. + */ +gboolean msn_servconn_connect(MsnServConn *servconn, const char *host, int port); + +/** + * Disconnects. + * + * @param servconn The connection. + */ +void msn_servconn_disconnect(MsnServConn *servconn); + +/** + * Sets the connect callback. + * + * @param servconn The servconn. + * @param connect_cb The connect callback. + */ +void msn_servconn_set_connect_cb(MsnServConn *servconn, + void (*connect_cb)(MsnServConn *)); +/** + * Sets the disconnect callback. + * + * @param servconn The servconn. + * @param disconnect_cb The disconnect callback. + */ +void msn_servconn_set_disconnect_cb(MsnServConn *servconn, + void (*disconnect_cb)(MsnServConn *)); +/** + * Sets the destroy callback. + * + * @param servconn The servconn that's being destroyed. + * @param destroy_cb The destroy callback. + */ +void msn_servconn_set_destroy_cb(MsnServConn *servconn, + void (*destroy_cb)(MsnServConn *)); + +/** + * Writes a chunck of data to the servconn. + * + * @param servconn The servconn. + * @param buf The data to write. + * @param size The size of the data. + */ +ssize_t msn_servconn_write(MsnServConn *servconn, const char *buf, + size_t size); + +/** + * Function to call whenever an error related to a switchboard occurs. + * + * @param servconn The servconn. + * @param error The error that happened. + */ +void msn_servconn_got_error(MsnServConn *servconn, MsnServConnError error); + +#endif /* _MSN_SERVCONN_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/session.c Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,431 @@ +/** + * @file session.c MSN session functions + * + * 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 "msn.h" +#include "session.h" +#include "notification.h" + +#include "dialog.h" + +MsnSession * +msn_session_new(PurpleAccount *account) +{ + MsnSession *session; + + g_return_val_if_fail(account != NULL, NULL); + + session = g_new0(MsnSession, 1); + + session->account = account; + session->notification = msn_notification_new(session); + session->userlist = msn_userlist_new(session); + + session->user = msn_user_new(session->userlist, + purple_account_get_username(account), NULL); + + session->protocol_ver = 9; + session->conv_seq = 1; + + return session; +} + +void +msn_session_destroy(MsnSession *session) +{ + g_return_if_fail(session != NULL); + + session->destroying = TRUE; + + if (session->connected) + msn_session_disconnect(session); + + if (session->notification != NULL) + msn_notification_destroy(session->notification); + + while (session->switches != NULL) + msn_switchboard_destroy(session->switches->data); + + while (session->slplinks != NULL) + msn_slplink_destroy(session->slplinks->data); + + msn_userlist_destroy(session->userlist); + + g_free(session->passport_info.kv); + g_free(session->passport_info.sid); + g_free(session->passport_info.mspauth); + g_free(session->passport_info.client_ip); + + if (session->passport_info.file != NULL) + { + g_unlink(session->passport_info.file); + g_free(session->passport_info.file); + } + + if (session->sync != NULL) + msn_sync_destroy(session->sync); + + if (session->nexus != NULL) + msn_nexus_destroy(session->nexus); + + if (session->user != NULL) + msn_user_destroy(session->user); + + g_free(session); +} + +gboolean +msn_session_connect(MsnSession *session, const char *host, int port, + gboolean http_method) +{ + g_return_val_if_fail(session != NULL, FALSE); + g_return_val_if_fail(!session->connected, TRUE); + + session->connected = TRUE; + session->http_method = http_method; + + if (session->notification == NULL) + { + purple_debug_error("msn", "This shouldn't happen\n"); + g_return_val_if_reached(FALSE); + } + + if (msn_notification_connect(session->notification, host, port)) + { + return TRUE; + } + + return FALSE; +} + +void +msn_session_disconnect(MsnSession *session) +{ + g_return_if_fail(session != NULL); + g_return_if_fail(session->connected); + + session->connected = FALSE; + + while (session->switches != NULL) + msn_switchboard_close(session->switches->data); + + if (session->notification != NULL) + msn_notification_close(session->notification); +} + +/* TODO: This must go away when conversation is redesigned */ +MsnSwitchBoard * +msn_session_find_swboard(MsnSession *session, const char *username) +{ + GList *l; + + g_return_val_if_fail(session != NULL, NULL); + g_return_val_if_fail(username != NULL, NULL); + + for (l = session->switches; l != NULL; l = l->next) + { + MsnSwitchBoard *swboard; + + swboard = l->data; + + if ((swboard->im_user != NULL) && !strcmp(username, swboard->im_user)) + return swboard; + } + + return NULL; +} + +MsnSwitchBoard * +msn_session_find_swboard_with_conv(MsnSession *session, PurpleConversation *conv) +{ + GList *l; + + g_return_val_if_fail(session != NULL, NULL); + g_return_val_if_fail(conv != NULL, NULL); + + for (l = session->switches; l != NULL; l = l->next) + { + MsnSwitchBoard *swboard; + + swboard = l->data; + + if (swboard->conv == conv) + return swboard; + } + + return NULL; +} + +MsnSwitchBoard * +msn_session_find_swboard_with_id(const MsnSession *session, int chat_id) +{ + GList *l; + + g_return_val_if_fail(session != NULL, NULL); + g_return_val_if_fail(chat_id >= 0, NULL); + + for (l = session->switches; l != NULL; l = l->next) + { + MsnSwitchBoard *swboard; + + swboard = l->data; + + if (swboard->chat_id == chat_id) + return swboard; + } + + return NULL; +} + +MsnSwitchBoard * +msn_session_get_swboard(MsnSession *session, const char *username, + MsnSBFlag flag) +{ + MsnSwitchBoard *swboard; + + g_return_val_if_fail(session != NULL, NULL); + + swboard = msn_session_find_swboard(session, username); + + if (swboard == NULL) + { + swboard = msn_switchboard_new(session); + swboard->im_user = g_strdup(username); + msn_switchboard_request(swboard); + msn_switchboard_request_add_user(swboard, username); + } + + swboard->flag |= flag; + + return swboard; +} + +static void +msn_session_sync_users(MsnSession *session) +{ + PurpleBlistNode *gnode, *cnode, *bnode; + PurpleConnection *gc = purple_account_get_connection(session->account); + + g_return_if_fail(gc != NULL); + + /* The core used to use msn_add_buddy to add all buddies before + * being logged in. This no longer happens, so we manually iterate + * over the whole buddy list to identify sync issues. */ + + for (gnode = purple_blist_get_root(); gnode; gnode = gnode->next) { + PurpleGroup *group = (PurpleGroup *)gnode; + const char *group_name = group->name; + if(!PURPLE_BLIST_NODE_IS_GROUP(gnode)) + continue; + for(cnode = gnode->child; cnode; cnode = cnode->next) { + if(!PURPLE_BLIST_NODE_IS_CONTACT(cnode)) + continue; + for(bnode = cnode->child; bnode; bnode = bnode->next) { + PurpleBuddy *b; + if(!PURPLE_BLIST_NODE_IS_BUDDY(bnode)) + continue; + b = (PurpleBuddy *)bnode; + if(purple_buddy_get_account(b) == purple_connection_get_account(gc)) { + MsnUser *remote_user; + gboolean found = FALSE; + + remote_user = msn_userlist_find_user(session->userlist, purple_buddy_get_name(b)); + + if ((remote_user != NULL) && (remote_user->list_op & MSN_LIST_FL_OP)) + { + int group_id; + GList *l; + + group_id = msn_userlist_find_group_id(remote_user->userlist, + group_name); + + for (l = remote_user->group_ids; l != NULL; l = l->next) + { + if (group_id == GPOINTER_TO_INT(l->data)) + { + found = TRUE; + break; + } + } + + } + + if (!found) + { + /* The user was not on the server list or not in that group + * on the server list */ + msn_show_sync_issue(session, purple_buddy_get_name(b), group_name); + } + } + } + } + } +} + +void +msn_session_set_error(MsnSession *session, MsnErrorType error, + const char *info) +{ + PurpleConnection *gc; + PurpleConnectionError reason; + char *msg; + + gc = purple_account_get_connection(session->account); + + switch (error) + { + case MSN_ERROR_SERVCONN: + reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; + msg = g_strdup(info); + break; + case MSN_ERROR_UNSUPPORTED_PROTOCOL: + reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; + msg = g_strdup(_("Our protocol is not supported by the " + "server.")); + break; + case MSN_ERROR_HTTP_MALFORMED: + reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; + msg = g_strdup(_("Error parsing HTTP.")); + break; + case MSN_ERROR_SIGN_OTHER: + reason = PURPLE_CONNECTION_ERROR_NAME_IN_USE; + msg = g_strdup(_("You have signed on from another location.")); + if (!purple_account_get_remember_password(session->account)) + purple_account_set_password(session->account, NULL); + break; + case MSN_ERROR_SERV_UNAVAILABLE: + reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; + msg = g_strdup(_("The MSN servers are temporarily " + "unavailable. Please wait and try " + "again.")); + break; + case MSN_ERROR_SERV_DOWN: + reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; + msg = g_strdup(_("The MSN servers are going down " + "temporarily.")); + break; + case MSN_ERROR_AUTH: + reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED; + msg = g_strdup_printf(_("Unable to authenticate: %s"), + (info == NULL ) ? + _("Unknown error") : info); + break; + case MSN_ERROR_BAD_BLIST: + reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; + msg = g_strdup(_("Your MSN buddy list is temporarily " + "unavailable. Please wait and try " + "again.")); + break; + default: + reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; + msg = g_strdup(_("Unknown error.")); + break; + } + + msn_session_disconnect(session); + + purple_connection_error_reason (gc, reason, msg); + + g_free(msg); +} + +static const char * +get_login_step_text(MsnSession *session) +{ + const char *steps_text[] = { + _("Connecting"), + _("Handshaking"), + _("Transferring"), + _("Handshaking"), + _("Starting authentication"), + _("Getting cookie"), + _("Authenticating"), + _("Sending cookie"), + _("Retrieving buddy list") + }; + + return steps_text[session->login_step]; +} + +void +msn_session_set_login_step(MsnSession *session, MsnLoginStep step) +{ + PurpleConnection *gc; + + /* Prevent the connection progress going backwards, eg. if we get + * transferred several times during login */ + if (session->login_step > step) + return; + + /* If we're already logged in, we're probably here because of a + * mid-session XFR from the notification server, so we don't want to + * popup the connection progress dialog */ + if (session->logged_in) + return; + + gc = session->account->gc; + + session->login_step = step; + + purple_connection_update_progress(gc, get_login_step_text(session), step, + MSN_LOGIN_STEPS); +} + +void +msn_session_finish_login(MsnSession *session) +{ + PurpleAccount *account; + PurpleConnection *gc; + PurpleStoredImage *img; + const char *passport; + + if (session->logged_in) + return; + + account = session->account; + gc = purple_account_get_connection(account); + + img = purple_buddy_icons_find_account_icon(session->account); + msn_user_set_buddy_icon(session->user, img); + purple_imgstore_unref(img); + + session->logged_in = TRUE; + + msn_change_status(session); + + purple_connection_set_state(gc, PURPLE_CONNECTED); + + /* Sync users */ + msn_session_sync_users(session); + /* It seems that some accounts that haven't accessed hotmail for a while + * and @msn.com accounts don't automatically get the initial email + * notification so we always request it on login + */ + + passport = purple_normalize(account, purple_account_get_username(account)); + + if ((strstr(passport, "@hotmail.") != NULL) || + (strstr(passport, "@msn.com") != NULL)) + { + msn_cmdproc_send(session->notification->cmdproc, "URL", "%s", "INBOX"); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/session.h Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,227 @@ +/** + * @file session.h MSN session functions + * + * 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 _MSN_SESSION_H_ +#define _MSN_SESSION_H_ + +typedef struct _MsnSession MsnSession; + +#include "sslconn.h" + +#include "user.h" +#include "slpcall.h" + +#include "notification.h" +#include "switchboard.h" +#include "group.h" + +#include "cmdproc.h" +#include "nexus.h" +#include "httpconn.h" + +#include "userlist.h" +#include "sync.h" + +/** + * Types of errors. + */ +typedef enum +{ + MSN_ERROR_SERVCONN, + MSN_ERROR_UNSUPPORTED_PROTOCOL, + MSN_ERROR_HTTP_MALFORMED, + MSN_ERROR_AUTH, + MSN_ERROR_BAD_BLIST, + MSN_ERROR_SIGN_OTHER, + MSN_ERROR_SERV_DOWN, + MSN_ERROR_SERV_UNAVAILABLE + +} MsnErrorType; + +/** + * Login steps. + */ +typedef enum +{ + MSN_LOGIN_STEP_START, + MSN_LOGIN_STEP_HANDSHAKE, + MSN_LOGIN_STEP_TRANSFER, + MSN_LOGIN_STEP_HANDSHAKE2, + MSN_LOGIN_STEP_AUTH_START, + MSN_LOGIN_STEP_AUTH, + MSN_LOGIN_STEP_GET_COOKIE, + MSN_LOGIN_STEP_AUTH_END, + MSN_LOGIN_STEP_SYN, + MSN_LOGIN_STEP_END + +} MsnLoginStep; + +#define MSN_LOGIN_STEPS MSN_LOGIN_STEP_END + +struct _MsnSession +{ + PurpleAccount *account; + MsnUser *user; + + guint protocol_ver; + + MsnLoginStep login_step; /**< The current step in the login process. */ + + gboolean connected; + gboolean logged_in; /**< A temporal flag to ignore local buddy list adds. */ + gboolean destroying; /**< A flag that states if the session is being destroyed. */ + gboolean http_method; + + MsnNotification *notification; + MsnNexus *nexus; + MsnSync *sync; + + MsnUserList *userlist; + + int servconns_count; /**< The count of server connections. */ + GList *switches; /**< The list of all the switchboards. */ + GList *directconns; /**< The list of all the directconnections. */ + GList *slplinks; /**< The list of all the slplinks. */ + + int conv_seq; /**< The current conversation sequence number. */ + + struct + { + char *kv; + char *sid; + char *mspauth; + unsigned long sl; + char *file; + char *client_ip; + int client_port; + + } passport_info; +}; + +/** + * Creates an MSN session. + * + * @param account The account. + * + * @return The new MSN session. + */ +MsnSession *msn_session_new(PurpleAccount *account); + +/** + * Destroys an MSN session. + * + * @param session The MSN session to destroy. + */ +void msn_session_destroy(MsnSession *session); + +/** + * Connects to and initiates an MSN session. + * + * @param session The MSN session. + * @param host The dispatch server host. + * @param port The dispatch server port. + * @param http_method Whether to use or not http_method. + * + * @return @c TRUE on success, @c FALSE on failure. + */ +gboolean msn_session_connect(MsnSession *session, + const char *host, int port, + gboolean http_method); + +/** + * Disconnects from an MSN session. + * + * @param session The MSN session. + */ +void msn_session_disconnect(MsnSession *session); + + /** + * Finds a switchboard with the given username. + * + * @param session The MSN session. + * @param username The username to search for. + * + * @return The switchboard, if found. + */ +MsnSwitchBoard *msn_session_find_swboard(MsnSession *session, + const char *username); + + /** + * Finds a switchboard with the given conversation. + * + * @param session The MSN session. + * @param conv The conversation to search for. + * + * @return The switchboard, if found. + */ +MsnSwitchBoard *msn_session_find_swboard_with_conv(MsnSession *session, + PurpleConversation *conv); +/** + * Finds a switchboard with the given chat ID. + * + * @param session The MSN session. + * @param chat_id The chat ID to search for. + * + * @return The switchboard, if found. + */ +MsnSwitchBoard *msn_session_find_swboard_with_id(const MsnSession *session, + int chat_id); + +/** + * Returns a switchboard to communicate with certain username. + * + * @param session The MSN session. + * @param username The username to search for. + * @param flag The flag of the switchboard + * + * @return The switchboard. + */ +MsnSwitchBoard *msn_session_get_swboard(MsnSession *session, + const char *username, MsnSBFlag flag); + +/** + * Sets an error for the MSN session. + * + * @param session The MSN session. + * @param error The error. + * @param info Extra information. + */ +void msn_session_set_error(MsnSession *session, MsnErrorType error, + const char *info); + +/** + * Sets the current step in the login proccess. + * + * @param session The MSN session. + * @param step The current step. + */ +void msn_session_set_login_step(MsnSession *session, MsnLoginStep step); + +/** + * Finish the login proccess. + * + * @param session The MSN session. + */ +void msn_session_finish_login(MsnSession *session); + +#endif /* _MSN_SESSION_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/slp.c Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,1109 @@ +/** + * @file msnslp.c MSNSLP support + * + * 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 "msn.h" +#include "slp.h" +#include "slpcall.h" +#include "slpmsg.h" +#include "slpsession.h" + +#include "object.h" +#include "user.h" +#include "switchboard.h" + +/* ms to delay between sending buddy icon requests to the server. */ +#define BUDDY_ICON_DELAY 20000 + +static void send_ok(MsnSlpCall *slpcall, const char *branch, + const char *type, const char *content); + +static void send_decline(MsnSlpCall *slpcall, const char *branch, + const char *type, const char *content); + +void msn_request_user_display(MsnUser *user); + +/************************************************************************** + * Util + **************************************************************************/ + +static char * +get_token(const char *str, const char *start, const char *end) +{ + const char *c, *c2; + + if ((c = strstr(str, start)) == NULL) + return NULL; + + c += strlen(start); + + if (end != NULL) + { + if ((c2 = strstr(c, end)) == NULL) + return NULL; + + return g_strndup(c, c2 - c); + } + else + { + /* This has to be changed */ + return g_strdup(c); + } + +} + +/************************************************************************** + * Xfer + **************************************************************************/ + +static void +msn_xfer_init(PurpleXfer *xfer) +{ + MsnSlpCall *slpcall; + /* MsnSlpLink *slplink; */ + char *content; + + purple_debug_info("msn", "xfer_init\n"); + + slpcall = xfer->data; + + /* Send Ok */ + content = g_strdup_printf("SessionID: %lu\r\n\r\n", + slpcall->session_id); + + send_ok(slpcall, slpcall->branch, "application/x-msnmsgr-sessionreqbody", + content); + + g_free(content); + msn_slplink_unleash(slpcall->slplink); +} + +void +msn_xfer_cancel(PurpleXfer *xfer) +{ + MsnSlpCall *slpcall; + char *content; + + g_return_if_fail(xfer != NULL); + g_return_if_fail(xfer->data != NULL); + + slpcall = xfer->data; + + if (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_CANCEL_LOCAL) + { + if (slpcall->started) + { + msn_slp_call_close(slpcall); + } + else + { + content = g_strdup_printf("SessionID: %lu\r\n\r\n", + slpcall->session_id); + + send_decline(slpcall, slpcall->branch, "application/x-msnmsgr-sessionreqbody", + content); + + g_free(content); + msn_slplink_unleash(slpcall->slplink); + + msn_slp_call_destroy(slpcall); + } + } +} + +void +msn_xfer_progress_cb(MsnSlpCall *slpcall, gsize total_length, gsize len, gsize offset) +{ + PurpleXfer *xfer; + + xfer = slpcall->xfer; + + xfer->bytes_sent = (offset + len); + xfer->bytes_remaining = total_length - (offset + len); + + purple_xfer_update_progress(xfer); +} + +void +msn_xfer_end_cb(MsnSlpCall *slpcall, MsnSession *session) +{ + if ((purple_xfer_get_status(slpcall->xfer) != PURPLE_XFER_STATUS_DONE) && + (purple_xfer_get_status(slpcall->xfer) != PURPLE_XFER_STATUS_CANCEL_REMOTE) && + (purple_xfer_get_status(slpcall->xfer) != PURPLE_XFER_STATUS_CANCEL_LOCAL)) + { + purple_xfer_cancel_remote(slpcall->xfer); + } +} + +void +msn_xfer_completed_cb(MsnSlpCall *slpcall, const guchar *body, + gsize size) +{ + PurpleXfer *xfer = slpcall->xfer; + purple_xfer_set_completed(xfer, TRUE); + purple_xfer_end(xfer); +} + +/************************************************************************** + * SLP Control + **************************************************************************/ + +#if 0 +static void +got_transresp(MsnSlpCall *slpcall, const char *nonce, + const char *ips_str, int port) +{ + MsnDirectConn *directconn; + char **ip_addrs, **c; + + directconn = msn_directconn_new(slpcall->slplink); + + directconn->initial_call = slpcall; + + /* msn_directconn_parse_nonce(directconn, nonce); */ + directconn->nonce = g_strdup(nonce); + + ip_addrs = g_strsplit(ips_str, " ", -1); + + for (c = ip_addrs; *c != NULL; c++) + { + purple_debug_info("msn", "ip_addr = %s\n", *c); + if (msn_directconn_connect(directconn, *c, port)) + break; + } + + g_strfreev(ip_addrs); +} +#endif + +static void +send_ok(MsnSlpCall *slpcall, const char *branch, + const char *type, const char *content) +{ + MsnSlpLink *slplink; + MsnSlpMessage *slpmsg; + + slplink = slpcall->slplink; + + /* 200 OK */ + slpmsg = msn_slpmsg_sip_new(slpcall, 1, + "MSNSLP/1.0 200 OK", + branch, type, content); + +#ifdef MSN_DEBUG_SLP + slpmsg->info = "SLP 200 OK"; + slpmsg->text_body = TRUE; +#endif + + msn_slplink_queue_slpmsg(slplink, slpmsg); + + msn_slp_call_session_init(slpcall); +} + +static void +send_decline(MsnSlpCall *slpcall, const char *branch, + const char *type, const char *content) +{ + MsnSlpLink *slplink; + MsnSlpMessage *slpmsg; + + slplink = slpcall->slplink; + + /* 603 Decline */ + slpmsg = msn_slpmsg_sip_new(slpcall, 1, + "MSNSLP/1.0 603 Decline", + branch, type, content); + +#ifdef MSN_DEBUG_SLP + slpmsg->info = "SLP 603 Decline"; + slpmsg->text_body = TRUE; +#endif + + msn_slplink_queue_slpmsg(slplink, slpmsg); +} + +#define MAX_FILE_NAME_LEN 0x226 + +static void +got_sessionreq(MsnSlpCall *slpcall, const char *branch, + const char *euf_guid, const char *context) +{ + if (!strcmp(euf_guid, "A4268EEC-FEC5-49E5-95C3-F126696BDBF6")) + { + /* Emoticon or UserDisplay */ + char *content; + gsize len; + MsnSlpSession *slpsession; + MsnSlpLink *slplink; + MsnSlpMessage *slpmsg; + MsnObject *obj; + char *msnobj_data; + PurpleStoredImage *img; + int type; + + /* Send Ok */ + content = g_strdup_printf("SessionID: %lu\r\n\r\n", + slpcall->session_id); + + send_ok(slpcall, branch, "application/x-msnmsgr-sessionreqbody", + content); + + g_free(content); + + slplink = slpcall->slplink; + + msnobj_data = (char *)purple_base64_decode(context, &len); + obj = msn_object_new_from_string(msnobj_data); + type = msn_object_get_type(obj); + g_free(msnobj_data); + + if (!(type == MSN_OBJECT_USERTILE)) + { + purple_debug_error("msn", "Wrong object?\n"); + msn_object_destroy(obj); + g_return_if_reached(); + } + + img = msn_object_get_image(obj); + if (img == NULL) + { + purple_debug_error("msn", "Wrong object.\n"); + msn_object_destroy(obj); + g_return_if_reached(); + } + + msn_object_destroy(obj); + + slpsession = msn_slplink_find_slp_session(slplink, + slpcall->session_id); + + /* DATA PREP */ + slpmsg = msn_slpmsg_new(slplink); + slpmsg->slpcall = slpcall; + slpmsg->slpsession = slpsession; + slpmsg->session_id = slpsession->id; + msn_slpmsg_set_body(slpmsg, NULL, 4); +#ifdef MSN_DEBUG_SLP + slpmsg->info = "SLP DATA PREP"; +#endif + msn_slplink_queue_slpmsg(slplink, slpmsg); + + /* DATA */ + slpmsg = msn_slpmsg_new(slplink); + slpmsg->slpcall = slpcall; + slpmsg->slpsession = slpsession; + slpmsg->flags = 0x20; +#ifdef MSN_DEBUG_SLP + slpmsg->info = "SLP DATA"; +#endif + msn_slpmsg_set_image(slpmsg, img); + msn_slplink_queue_slpmsg(slplink, slpmsg); + } + else if (!strcmp(euf_guid, "5D3E02AB-6190-11D3-BBBB-00C04F795683")) + { + /* File Transfer */ + PurpleAccount *account; + PurpleXfer *xfer; + char *bin; + gsize bin_len; + guint32 file_size; + char *file_name; + gunichar2 *uni_name; + + account = slpcall->slplink->session->account; + + slpcall->cb = msn_xfer_completed_cb; + slpcall->end_cb = msn_xfer_end_cb; + slpcall->progress_cb = msn_xfer_progress_cb; + slpcall->branch = g_strdup(branch); + + slpcall->pending = TRUE; + + xfer = purple_xfer_new(account, PURPLE_XFER_RECEIVE, + slpcall->slplink->remote_user); + if (xfer) + { + bin = (char *)purple_base64_decode(context, &bin_len); + file_size = GUINT32_FROM_LE(*(gsize *)(bin + 8)); + + uni_name = (gunichar2 *)(bin + 20); + while(*uni_name != 0 && ((char *)uni_name - (bin + 20)) < MAX_FILE_NAME_LEN) { + *uni_name = GUINT16_FROM_LE(*uni_name); + uni_name++; + } + + file_name = g_utf16_to_utf8((const gunichar2 *)(bin + 20), -1, + NULL, NULL, NULL); + + g_free(bin); + + purple_xfer_set_filename(xfer, file_name); + purple_xfer_set_size(xfer, file_size); + purple_xfer_set_init_fnc(xfer, msn_xfer_init); + purple_xfer_set_request_denied_fnc(xfer, msn_xfer_cancel); + purple_xfer_set_cancel_recv_fnc(xfer, msn_xfer_cancel); + + slpcall->xfer = xfer; + purple_xfer_ref(slpcall->xfer); + + xfer->data = slpcall; + + purple_xfer_request(xfer); + } + } +} + +void +send_bye(MsnSlpCall *slpcall, const char *type) +{ + MsnSlpLink *slplink; + MsnSlpMessage *slpmsg; + char *header; + + slplink = slpcall->slplink; + + g_return_if_fail(slplink != NULL); + + header = g_strdup_printf("BYE MSNMSGR:%s MSNSLP/1.0", + slplink->local_user); + + slpmsg = msn_slpmsg_sip_new(slpcall, 0, header, + "A0D624A6-6C0C-4283-A9E0-BC97B4B46D32", + type, + "\r\n"); + g_free(header); + +#ifdef MSN_DEBUG_SLP + slpmsg->info = "SLP BYE"; + slpmsg->text_body = TRUE; +#endif + + msn_slplink_queue_slpmsg(slplink, slpmsg); +} + +static void +got_invite(MsnSlpCall *slpcall, + const char *branch, const char *type, const char *content) +{ + MsnSlpLink *slplink; + + slplink = slpcall->slplink; + + if (!strcmp(type, "application/x-msnmsgr-sessionreqbody")) + { + char *euf_guid, *context; + char *temp; + + euf_guid = get_token(content, "EUF-GUID: {", "}\r\n"); + + temp = get_token(content, "SessionID: ", "\r\n"); + if (temp != NULL) + slpcall->session_id = atoi(temp); + g_free(temp); + + temp = get_token(content, "AppID: ", "\r\n"); + if (temp != NULL) + slpcall->app_id = atoi(temp); + g_free(temp); + + context = get_token(content, "Context: ", "\r\n"); + + if (context != NULL) + got_sessionreq(slpcall, branch, euf_guid, context); + + g_free(context); + g_free(euf_guid); + } + else if (!strcmp(type, "application/x-msnmsgr-transreqbody")) + { + /* A direct connection? */ + + char *listening, *nonce; + char *content; + + if (FALSE) + { +#if 0 + MsnDirectConn *directconn; + /* const char *ip_addr; */ + char *ip_port; + int port; + + /* ip_addr = purple_prefs_get_string("/purple/ft/public_ip"); */ + ip_port = "5190"; + listening = "true"; + nonce = rand_guid(); + + directconn = msn_directconn_new(slplink); + + /* msn_directconn_parse_nonce(directconn, nonce); */ + directconn->nonce = g_strdup(nonce); + + msn_directconn_listen(directconn); + + port = directconn->port; + + content = g_strdup_printf( + "Bridge: TCPv1\r\n" + "Listening: %s\r\n" + "Nonce: {%s}\r\n" + "Ipv4Internal-Addrs: 192.168.0.82\r\n" + "Ipv4Internal-Port: %d\r\n" + "\r\n", + listening, + nonce, + port); +#endif + } + else + { + listening = "false"; + nonce = g_strdup("00000000-0000-0000-0000-000000000000"); + + content = g_strdup_printf( + "Bridge: TCPv1\r\n" + "Listening: %s\r\n" + "Nonce: {%s}\r\n" + "\r\n", + listening, + nonce); + } + + send_ok(slpcall, branch, + "application/x-msnmsgr-transrespbody", content); + + g_free(content); + g_free(nonce); + } + else if (!strcmp(type, "application/x-msnmsgr-transrespbody")) + { +#if 0 + char *ip_addrs; + char *temp; + char *nonce; + int port; + + nonce = get_token(content, "Nonce: {", "}\r\n"); + ip_addrs = get_token(content, "IPv4Internal-Addrs: ", "\r\n"); + + temp = get_token(content, "IPv4Internal-Port: ", "\r\n"); + if (temp != NULL) + port = atoi(temp); + else + port = -1; + g_free(temp); + + if (ip_addrs == NULL) + return; + + if (port > 0) + got_transresp(slpcall, nonce, ip_addrs, port); + + g_free(nonce); + g_free(ip_addrs); +#endif + } +} + +static void +got_ok(MsnSlpCall *slpcall, + const char *type, const char *content) +{ + g_return_if_fail(slpcall != NULL); + g_return_if_fail(type != NULL); + + if (!strcmp(type, "application/x-msnmsgr-sessionreqbody")) + { +#if 0 + if (slpcall->type == MSN_SLPCALL_DC) + { + /* First let's try a DirectConnection. */ + + MsnSlpLink *slplink; + MsnSlpMessage *slpmsg; + char *header; + char *content; + char *branch; + + slplink = slpcall->slplink; + + branch = rand_guid(); + + content = g_strdup_printf( + "Bridges: TRUDPv1 TCPv1\r\n" + "NetID: 0\r\n" + "Conn-Type: Direct-Connect\r\n" + "UPnPNat: false\r\n" + "ICF: false\r\n" + ); + + header = g_strdup_printf("INVITE MSNMSGR:%s MSNSLP/1.0", + slplink->remote_user); + + slpmsg = msn_slp_sipmsg_new(slpcall, 0, header, branch, + "application/x-msnmsgr-transreqbody", + content); + +#ifdef MSN_DEBUG_SLP + slpmsg->info = "SLP INVITE"; + slpmsg->text_body = TRUE; +#endif + msn_slplink_send_slpmsg(slplink, slpmsg); + + g_free(header); + g_free(content); + + g_free(branch); + } + else + { + msn_slp_call_session_init(slpcall); + } +#else + msn_slp_call_session_init(slpcall); +#endif + } + else if (!strcmp(type, "application/x-msnmsgr-transreqbody")) + { + /* Do we get this? */ + purple_debug_info("msn", "OK with transreqbody\n"); + } + else if (!strcmp(type, "application/x-msnmsgr-transrespbody")) + { +#if 0 + char *ip_addrs; + char *temp; + char *nonce; + int port; + + nonce = get_token(content, "Nonce: {", "}\r\n"); + ip_addrs = get_token(content, "IPv4Internal-Addrs: ", "\r\n"); + + temp = get_token(content, "IPv4Internal-Port: ", "\r\n"); + if (temp != NULL) + port = atoi(temp); + else + port = -1; + g_free(temp); + + if (ip_addrs == NULL) + return; + + if (port > 0) + got_transresp(slpcall, nonce, ip_addrs, port); + + g_free(nonce); + g_free(ip_addrs); +#endif + } +} + +MsnSlpCall * +msn_slp_sip_recv(MsnSlpLink *slplink, const char *body) +{ + MsnSlpCall *slpcall; + + if (body == NULL) + { + purple_debug_warning("msn", "received bogus message\n"); + return NULL; + } + + if (!strncmp(body, "INVITE", strlen("INVITE"))) + { + char *branch; + char *content; + char *content_type; + + slpcall = msn_slp_call_new(slplink); + + /* From: <msnmsgr:buddy@hotmail.com> */ +#if 0 + slpcall->remote_user = get_token(body, "From: <msnmsgr:", ">\r\n"); +#endif + + branch = get_token(body, ";branch={", "}"); + + slpcall->id = get_token(body, "Call-ID: {", "}"); + +#if 0 + long content_len = -1; + + temp = get_token(body, "Content-Length: ", "\r\n"); + if (temp != NULL) + content_len = atoi(temp); + g_free(temp); +#endif + content_type = get_token(body, "Content-Type: ", "\r\n"); + + content = get_token(body, "\r\n\r\n", NULL); + + got_invite(slpcall, branch, content_type, content); + + g_free(branch); + g_free(content_type); + g_free(content); + } + else if (!strncmp(body, "MSNSLP/1.0 ", strlen("MSNSLP/1.0 "))) + { + char *content; + char *content_type; + /* Make sure this is "OK" */ + const char *status = body + strlen("MSNSLP/1.0 "); + char *call_id; + + call_id = get_token(body, "Call-ID: {", "}"); + slpcall = msn_slplink_find_slp_call(slplink, call_id); + g_free(call_id); + + g_return_val_if_fail(slpcall != NULL, NULL); + + if (strncmp(status, "200 OK", 6)) + { + /* It's not valid. Kill this off. */ + char temp[32]; + const char *c; + + /* Eww */ + if ((c = strchr(status, '\r')) || (c = strchr(status, '\n')) || + (c = strchr(status, '\0'))) + { + size_t offset = c - status; + if (offset >= sizeof(temp)) + offset = sizeof(temp) - 1; + + strncpy(temp, status, offset); + temp[offset] = '\0'; + } + + purple_debug_error("msn", "Received non-OK result: %s\n", temp); + + slpcall->wasted = TRUE; + + /* msn_slp_call_destroy(slpcall); */ + return slpcall; + } + + content_type = get_token(body, "Content-Type: ", "\r\n"); + + content = get_token(body, "\r\n\r\n", NULL); + + got_ok(slpcall, content_type, content); + + g_free(content_type); + g_free(content); + } + else if (!strncmp(body, "BYE", strlen("BYE"))) + { + char *call_id; + + call_id = get_token(body, "Call-ID: {", "}"); + slpcall = msn_slplink_find_slp_call(slplink, call_id); + g_free(call_id); + + if (slpcall != NULL) + slpcall->wasted = TRUE; + + /* msn_slp_call_destroy(slpcall); */ + } + else + slpcall = NULL; + + return slpcall; +} + +/************************************************************************** + * Msg Callbacks + **************************************************************************/ + +void +msn_p2p_msg(MsnCmdProc *cmdproc, MsnMessage *msg) +{ + MsnSession *session; + MsnSlpLink *slplink; + + session = cmdproc->servconn->session; + slplink = msn_session_get_slplink(session, msg->remote_user); + + if (slplink->swboard == NULL) + { + /* We will need this in order to change its flags. */ + slplink->swboard = (MsnSwitchBoard *)cmdproc->data; + /* If swboard is NULL, something has probably gone wrong earlier on + * I didn't want to do this, but MSN 7 is somehow causing us to crash + * here, I couldn't reproduce it to debug more, and people are + * reporting bugs. Hopefully this doesn't cause more crashes. Stu. + */ + if (slplink->swboard != NULL) + slplink->swboard->slplinks = g_list_prepend(slplink->swboard->slplinks, slplink); + else + purple_debug_error("msn", "msn_p2p_msg, swboard is NULL, ouch!\n"); + } + + msn_slplink_process_msg(slplink, msg); +} + +static void +got_emoticon(MsnSlpCall *slpcall, + const guchar *data, gsize size) +{ + + PurpleConversation *conv; + PurpleConnection *gc; + const char *who; + + gc = slpcall->slplink->session->account->gc; + who = slpcall->slplink->remote_user; + + if ((conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, who, gc->account))) { + + /* FIXME: it would be better if we wrote the data as we received it + instead of all at once, calling write multiple times and + close once at the very end + */ + purple_conv_custom_smiley_write(conv, slpcall->data_info, data, size); + purple_conv_custom_smiley_close(conv, slpcall->data_info); + } +#ifdef MSN_DEBUG_UD + purple_debug_info("msn", "Got smiley: %s\n", slpcall->data_info); +#endif +} + +void +msn_emoticon_msg(MsnCmdProc *cmdproc, MsnMessage *msg) +{ + MsnSession *session; + MsnSlpLink *slplink; + MsnObject *obj; + char **tokens; + char *smile, *body_str; + const char *body, *who, *sha1; + guint tok; + size_t body_len; + + PurpleConversation *conv; + + session = cmdproc->servconn->session; + + if (!purple_account_get_bool(session->account, "custom_smileys", TRUE)) + return; + + body = msn_message_get_bin_data(msg, &body_len); + body_str = g_strndup(body, body_len); + + /* MSN Messenger 7 may send more than one MSNObject in a single message... + * Maybe 10 tokens is a reasonable max value. */ + tokens = g_strsplit(body_str, "\t", 10); + + g_free(body_str); + + for (tok = 0; tok < 9; tok += 2) { + if (tokens[tok] == NULL || tokens[tok + 1] == NULL) { + break; + } + + smile = tokens[tok]; + obj = msn_object_new_from_string(purple_url_decode(tokens[tok + 1])); + + if (obj == NULL) + break; + + who = msn_object_get_creator(obj); + sha1 = msn_object_get_sha1(obj); + + slplink = msn_session_get_slplink(session, who); + + conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, who, + session->account); + + /* If the conversation doesn't exist then this is a custom smiley + * used in the first message in a MSN conversation: we need to create + * the conversation now, otherwise the custom smiley won't be shown. + * This happens because every GtkIMHtml has its own smiley tree: if + * the conversation doesn't exist then we cannot associate the new + * smiley with its GtkIMHtml widget. */ + if (!conv) { + conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, session->account, who); + } + + if (purple_conv_custom_smiley_add(conv, smile, "sha1", sha1, TRUE)) { + msn_slplink_request_object(slplink, smile, got_emoticon, NULL, obj); + } + + msn_object_destroy(obj); + obj = NULL; + who = NULL; + sha1 = NULL; + } + g_strfreev(tokens); +} + +static gboolean +buddy_icon_cached(PurpleConnection *gc, MsnObject *obj) +{ + PurpleAccount *account; + PurpleBuddy *buddy; + const char *old; + const char *new; + + g_return_val_if_fail(obj != NULL, FALSE); + + account = purple_connection_get_account(gc); + + buddy = purple_find_buddy(account, msn_object_get_creator(obj)); + if (buddy == NULL) + return FALSE; + + old = purple_buddy_icons_get_checksum_for_user(buddy); + new = msn_object_get_sha1(obj); + + if (new == NULL) + return FALSE; + + /* If the old and new checksums are the same, and the file actually exists, + * then return TRUE */ + if (old != NULL && !strcmp(old, new)) + return TRUE; + + return FALSE; +} + +static void +msn_release_buddy_icon_request(MsnUserList *userlist) +{ + MsnUser *user; + + g_return_if_fail(userlist != NULL); + +#ifdef MSN_DEBUG_UD + purple_debug_info("msn", "Releasing buddy icon request\n"); +#endif + + if (userlist->buddy_icon_window > 0) + { + GQueue *queue; + PurpleAccount *account; + const char *username; + + queue = userlist->buddy_icon_requests; + + if (g_queue_is_empty(userlist->buddy_icon_requests)) + return; + + user = g_queue_pop_head(queue); + + account = userlist->session->account; + username = user->passport; + + userlist->buddy_icon_window--; + msn_request_user_display(user); + +#ifdef MSN_DEBUG_UD + purple_debug_info("msn", "msn_release_buddy_icon_request(): buddy_icon_window-- yields =%d\n", + userlist->buddy_icon_window); +#endif + } +} + +/* + * Called on a timeout from end_user_display(). Frees a buddy icon window slow and dequeues the next + * buddy icon request if there is one. + */ +static gboolean +msn_release_buddy_icon_request_timeout(gpointer data) +{ + MsnUserList *userlist = (MsnUserList *)data; + + /* Free one window slot */ + userlist->buddy_icon_window++; + + /* Clear the tag for our former request timer */ + userlist->buddy_icon_request_timer = 0; + + msn_release_buddy_icon_request(userlist); + + return FALSE; +} + +void +msn_queue_buddy_icon_request(MsnUser *user) +{ + PurpleAccount *account; + MsnObject *obj; + GQueue *queue; + + g_return_if_fail(user != NULL); + + account = user->userlist->session->account; + + obj = msn_user_get_object(user); + + if (obj == NULL) + { + purple_buddy_icons_set_for_user(account, user->passport, NULL, 0, NULL); + return; + } + + if (!buddy_icon_cached(account->gc, obj)) + { + MsnUserList *userlist; + + userlist = user->userlist; + queue = userlist->buddy_icon_requests; + +#ifdef MSN_DEBUG_UD + purple_debug_info("msn", "Queueing buddy icon request for %s (buddy_icon_window = %i)\n", + user->passport, userlist->buddy_icon_window); +#endif + + g_queue_push_tail(queue, user); + + if (userlist->buddy_icon_window > 0) + msn_release_buddy_icon_request(userlist); + } +} + +static void +got_user_display(MsnSlpCall *slpcall, + const guchar *data, gsize size) +{ + MsnUserList *userlist; + const char *info; + PurpleAccount *account; + + g_return_if_fail(slpcall != NULL); + + info = slpcall->data_info; +#ifdef MSN_DEBUG_UD + purple_debug_info("msn", "Got User Display: %s\n", slpcall->slplink->remote_user); +#endif + + userlist = slpcall->slplink->session->userlist; + account = slpcall->slplink->session->account; + + purple_buddy_icons_set_for_user(account, slpcall->slplink->remote_user, + g_memdup(data, size), size, info); + +#if 0 + /* Free one window slot */ + userlist->buddy_icon_window++; + + purple_debug_info("msn", "got_user_display(): buddy_icon_window++ yields =%d\n", + userlist->buddy_icon_window); + + msn_release_buddy_icon_request(userlist); +#endif +} + +static void +end_user_display(MsnSlpCall *slpcall, MsnSession *session) +{ + MsnUserList *userlist; + + g_return_if_fail(session != NULL); + +#ifdef MSN_DEBUG_UD + purple_debug_info("msn", "End User Display\n"); +#endif + + userlist = session->userlist; + + /* If the session is being destroyed we better stop doing anything. */ + if (session->destroying) + return; + + /* Delay before freeing a buddy icon window slot and requesting the next icon, if appropriate. + * If we don't delay, we'll rapidly hit the MSN equivalent of AIM's rate limiting; the server will + * send us an error 800 like so: + * + * C: NS 000: XFR 21 SB + * S: NS 000: 800 21 + */ + if (userlist->buddy_icon_request_timer) { + /* Free the window slot used by this previous request */ + userlist->buddy_icon_window++; + + /* Clear our pending timeout */ + purple_timeout_remove(userlist->buddy_icon_request_timer); + } + + /* Wait BUDDY_ICON_DELAY ms before freeing our window slot and requesting the next icon. */ + userlist->buddy_icon_request_timer = purple_timeout_add(BUDDY_ICON_DELAY, + msn_release_buddy_icon_request_timeout, userlist); +} + +void +msn_request_user_display(MsnUser *user) +{ + PurpleAccount *account; + MsnSession *session; + MsnSlpLink *slplink; + MsnObject *obj; + const char *info; + + session = user->userlist->session; + account = session->account; + + slplink = msn_session_get_slplink(session, user->passport); + + obj = msn_user_get_object(user); + + info = msn_object_get_sha1(obj); + + if (g_ascii_strcasecmp(user->passport, + purple_account_get_username(account))) + { + msn_slplink_request_object(slplink, info, got_user_display, + end_user_display, obj); + } + else + { + MsnObject *my_obj = NULL; + gconstpointer data = NULL; + size_t len = 0; + +#ifdef MSN_DEBUG_UD + purple_debug_info("msn", "Requesting our own user display\n"); +#endif + + my_obj = msn_user_get_object(session->user); + + if (my_obj != NULL) + { + PurpleStoredImage *img = msn_object_get_image(my_obj); + data = purple_imgstore_get_data(img); + len = purple_imgstore_get_size(img); + } + + purple_buddy_icons_set_for_user(account, user->passport, g_memdup(data, len), len, info); + + /* Free one window slot */ + session->userlist->buddy_icon_window++; + +#ifdef MSN_DEBUG_UD + purple_debug_info("msn", "msn_request_user_display(): buddy_icon_window++ yields =%d\n", + session->userlist->buddy_icon_window); +#endif + + msn_release_buddy_icon_request(session->userlist); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/slp.h Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,48 @@ +/** + * @file slp.h MSNSLP support + * + * 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 _MSN_SLP_H_ +#define _MSN_SLP_H_ + +#include "slpcall.h" +#include "session.h" +#include "internal.h" +#include "ft.h" + +void msn_xfer_progress_cb(MsnSlpCall *slpcall, gsize total_length, gsize + len, gsize offset); + +MsnSlpCall * msn_slp_sip_recv(MsnSlpLink *slplink, + const char *body); + +void send_bye(MsnSlpCall *slpcall, const char *type); + +void msn_xfer_completed_cb(MsnSlpCall *slpcall, + const guchar *body, gsize size); + +void msn_xfer_cancel(PurpleXfer *xfer); +void msn_xfer_end_cb(MsnSlpCall *slpcall, MsnSession *session); + +void msn_queue_buddy_icon_request(MsnUser *user); + +#endif /* _MSN_SLP_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/slpcall.c Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,270 @@ +/** + * @file slpcall.c SLP Call Functions + * + * 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 "msn.h" +#include "slpcall.h" +#include "slpsession.h" + +#include "slp.h" + +/* #define MSN_DEBUG_SLPCALL */ + +/************************************************************************** + * Util + **************************************************************************/ + +static char * +rand_guid() +{ + return g_strdup_printf("%4X%4X-%4X-%4X-%4X-%4X%4X%4X", + rand() % 0xAAFF + 0x1111, + rand() % 0xAAFF + 0x1111, + rand() % 0xAAFF + 0x1111, + rand() % 0xAAFF + 0x1111, + rand() % 0xAAFF + 0x1111, + rand() % 0xAAFF + 0x1111, + rand() % 0xAAFF + 0x1111, + rand() % 0xAAFF + 0x1111); +} + +/************************************************************************** + * Main + **************************************************************************/ + +MsnSlpCall * +msn_slp_call_new(MsnSlpLink *slplink) +{ + MsnSlpCall *slpcall; + + g_return_val_if_fail(slplink != NULL, NULL); + + slpcall = g_new0(MsnSlpCall, 1); + +#ifdef MSN_DEBUG_SLPCALL + purple_debug_info("msn", "slpcall_new: slpcall(%p)\n", slpcall); +#endif + + slpcall->slplink = slplink; + + msn_slplink_add_slpcall(slplink, slpcall); + + slpcall->timer = purple_timeout_add(MSN_SLPCALL_TIMEOUT, msn_slp_call_timeout, slpcall); + + return slpcall; +} + +void +msn_slp_call_destroy(MsnSlpCall *slpcall) +{ + GList *e; + MsnSession *session; + +#ifdef MSN_DEBUG_SLPCALL + purple_debug_info("msn", "slpcall_destroy: slpcall(%p)\n", slpcall); +#endif + + g_return_if_fail(slpcall != NULL); + + if (slpcall->timer) + purple_timeout_remove(slpcall->timer); + + if (slpcall->id != NULL) + g_free(slpcall->id); + + if (slpcall->branch != NULL) + g_free(slpcall->branch); + + if (slpcall->data_info != NULL) + g_free(slpcall->data_info); + + for (e = slpcall->slplink->slp_msgs; e != NULL; ) + { + MsnSlpMessage *slpmsg = e->data; + e = e->next; + +#ifdef MSN_DEBUG_SLPCALL_VERBOSE + purple_debug_info("msn", "slpcall_destroy: trying slpmsg(%p)\n", + slpmsg); +#endif + + if (slpmsg->slpcall == slpcall) + { + msn_slpmsg_destroy(slpmsg); + } + } + + session = slpcall->slplink->session; + + msn_slplink_remove_slpcall(slpcall->slplink, slpcall); + + if (slpcall->end_cb != NULL) + slpcall->end_cb(slpcall, session); + + if (slpcall->xfer != NULL) + purple_xfer_unref(slpcall->xfer); + + g_free(slpcall); +} + +void +msn_slp_call_init(MsnSlpCall *slpcall, MsnSlpCallType type) +{ + slpcall->session_id = rand() % 0xFFFFFF00 + 4; + slpcall->id = rand_guid(); + slpcall->type = type; +} + +void +msn_slp_call_session_init(MsnSlpCall *slpcall) +{ + MsnSlpSession *slpsession; + + slpsession = msn_slp_session_new(slpcall); + + if (slpcall->session_init_cb) + slpcall->session_init_cb(slpsession); + + slpcall->started = TRUE; +} + +void +msn_slp_call_invite(MsnSlpCall *slpcall, const char *euf_guid, + int app_id, const char *context) +{ + MsnSlpLink *slplink; + MsnSlpMessage *slpmsg; + char *header; + char *content; + + g_return_if_fail(slpcall != NULL); + g_return_if_fail(context != NULL); + + slplink = slpcall->slplink; + + slpcall->branch = rand_guid(); + + content = g_strdup_printf( + "EUF-GUID: {%s}\r\n" + "SessionID: %lu\r\n" + "AppID: %d\r\n" + "Context: %s\r\n\r\n", + euf_guid, + slpcall->session_id, + app_id, + context); + + header = g_strdup_printf("INVITE MSNMSGR:%s MSNSLP/1.0", + slplink->remote_user); + + slpmsg = msn_slpmsg_sip_new(slpcall, 0, header, slpcall->branch, + "application/x-msnmsgr-sessionreqbody", content); + +#ifdef MSN_DEBUG_SLP + slpmsg->info = "SLP INVITE"; + slpmsg->text_body = TRUE; +#endif + + msn_slplink_send_slpmsg(slplink, slpmsg); + + g_free(header); + g_free(content); +} + +void +msn_slp_call_close(MsnSlpCall *slpcall) +{ + g_return_if_fail(slpcall != NULL); + g_return_if_fail(slpcall->slplink != NULL); + + send_bye(slpcall, "application/x-msnmsgr-sessionclosebody"); + msn_slplink_unleash(slpcall->slplink); + msn_slp_call_destroy(slpcall); +} + +gboolean +msn_slp_call_timeout(gpointer data) +{ + MsnSlpCall *slpcall; + + slpcall = data; + +#ifdef MSN_DEBUG_SLPCALL + purple_debug_info("msn", "slpcall_timeout: slpcall(%p)\n", slpcall); +#endif + + if (!slpcall->pending && !slpcall->progress) + { + msn_slp_call_destroy(slpcall); + return FALSE; + } + + slpcall->progress = FALSE; + + return TRUE; +} + +MsnSlpCall * +msn_slp_process_msg(MsnSlpLink *slplink, MsnSlpMessage *slpmsg) +{ + MsnSlpCall *slpcall; + const guchar *body; + gsize body_len; + + slpcall = NULL; + body = slpmsg->buffer; + body_len = slpmsg->size; + + if (slpmsg->flags == 0x0) + { + char *body_str; + + body_str = g_strndup((const char *)body, body_len); + slpcall = msn_slp_sip_recv(slplink, body_str); + g_free(body_str); + } + else if (slpmsg->flags == 0x20 || slpmsg->flags == 0x1000030) + { + slpcall = msn_slplink_find_slp_call_with_session_id(slplink, slpmsg->session_id); + + if (slpcall != NULL) + { + if (slpcall->timer) + purple_timeout_remove(slpcall->timer); + + slpcall->cb(slpcall, body, body_len); + + slpcall->wasted = TRUE; + } + } +#if 0 + else if (slpmsg->flags == 0x100) + { + slpcall = slplink->directconn->initial_call; + + if (slpcall != NULL) + msn_slp_call_session_init(slpcall); + } +#endif + + return slpcall; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/slpcall.h Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,91 @@ +/** + * @file slpcall.h SLP Call functions + * + * 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 _MSN_SLPCALL_H_ +#define _MSN_SLPCALL_H_ + +#include "internal.h" + +typedef struct _MsnSlpCall MsnSlpCall; + +#include "slplink.h" +#include "slpsession.h" + +/* The official client seems to timeout slp calls after 5 minutes */ +#define MSN_SLPCALL_TIMEOUT 300000 + +typedef enum +{ + MSN_SLPCALL_ANY, + MSN_SLPCALL_DC, + +} MsnSlpCallType; + +struct _MsnSlpCall +{ + /* MsnSession *session; */ + MsnSlpLink *slplink; + + MsnSlpCallType type; + + /* Call-ID */ + char *id; + char *branch; + + long session_id; + long app_id; + + gboolean pending; /**< A flag that states if we should wait for this + slpcall to start and do not time out. */ + gboolean progress; /**< A flag that states if there has been progress since + the last time out. */ + gboolean wasted; /**< A flag that states if this slpcall is going to be + destroyed. */ + gboolean started; /**< A flag that states if this slpcall's session has + been initiated. */ + + void (*progress_cb)(MsnSlpCall *slpcall, + gsize total_length, gsize len, gsize offset); + void (*session_init_cb)(MsnSlpSession *slpsession); + + /* Can be checksum, or smile */ + char *data_info; + + void *xfer; + + MsnSlpCb cb; + void (*end_cb)(MsnSlpCall *slpcall, MsnSession *session); + + int timer; +}; + +MsnSlpCall *msn_slp_call_new(MsnSlpLink *slplink); +void msn_slp_call_init(MsnSlpCall *slpcall, MsnSlpCallType type); +void msn_slp_call_session_init(MsnSlpCall *slpcall); +void msn_slp_call_destroy(MsnSlpCall *slpcall); +void msn_slp_call_invite(MsnSlpCall *slpcall, const char *euf_guid, + int app_id, const char *context); +void msn_slp_call_close(MsnSlpCall *slpcall); +gboolean msn_slp_call_timeout(gpointer data); + +#endif /* _MSN_SLPCALL_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/slplink.c Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,811 @@ +/** + * @file slplink.c MSNSLP Link support + * + * 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 "msn.h" +#include "slplink.h" + +#include "switchboard.h" +#include "slp.h" + +void msn_slplink_send_msgpart(MsnSlpLink *slplink, MsnSlpMessage *slpmsg); + +#ifdef MSN_DEBUG_SLP_FILES +static int m_sc = 0; +static int m_rc = 0; + +static void +debug_msg_to_file(MsnMessage *msg, gboolean send) +{ + char *tmp; + char *dir; + char *pload; + FILE *tf; + int c; + gsize pload_size; + + dir = send ? "send" : "recv"; + c = send ? m_sc++ : m_rc++; + tmp = g_strdup_printf("%s/msntest/%s/%03d", g_get_home_dir(), dir, c); + tf = g_fopen(tmp, "wb"); + if (tf == NULL) + { + purple_debug_error("msn", "could not open debug file\n"); + return; + } + pload = msn_message_gen_payload(msg, &pload_size); + fwrite(pload, 1, pload_size, tf); + fclose(tf); + g_free(tmp); +} +#endif + +/************************************************************************** + * Main + **************************************************************************/ + +MsnSlpLink * +msn_slplink_new(MsnSession *session, const char *username) +{ + MsnSlpLink *slplink; + + g_return_val_if_fail(session != NULL, NULL); + + slplink = g_new0(MsnSlpLink, 1); + +#ifdef MSN_DEBUG_SLPLINK + purple_debug_info("msn", "slplink_new: slplink(%p)\n", slplink); +#endif + + slplink->session = session; + slplink->slp_seq_id = rand() % 0xFFFFFF00 + 4; + + slplink->local_user = g_strdup(msn_user_get_passport(session->user)); + slplink->remote_user = g_strdup(username); + + slplink->slp_msg_queue = g_queue_new(); + + session->slplinks = + g_list_append(session->slplinks, slplink); + + return slplink; +} + +void +msn_slplink_destroy(MsnSlpLink *slplink) +{ + MsnSession *session; + +#ifdef MSN_DEBUG_SLPLINK + purple_debug_info("msn", "slplink_destroy: slplink(%p)\n", slplink); +#endif + + g_return_if_fail(slplink != NULL); + + if (slplink->swboard != NULL) + slplink->swboard->slplinks = g_list_remove(slplink->swboard->slplinks, slplink); + + session = slplink->session; + + if (slplink->local_user != NULL) + g_free(slplink->local_user); + + if (slplink->remote_user != NULL) + g_free(slplink->remote_user); + + if (slplink->directconn != NULL) + msn_directconn_destroy(slplink->directconn); + + while (slplink->slp_calls != NULL) + msn_slp_call_destroy(slplink->slp_calls->data); + + session->slplinks = + g_list_remove(session->slplinks, slplink); + + g_free(slplink); +} + +MsnSlpLink * +msn_session_find_slplink(MsnSession *session, const char *who) +{ + GList *l; + + for (l = session->slplinks; l != NULL; l = l->next) + { + MsnSlpLink *slplink; + + slplink = l->data; + + if (!strcmp(slplink->remote_user, who)) + return slplink; + } + + return NULL; +} + +MsnSlpLink * +msn_session_get_slplink(MsnSession *session, const char *username) +{ + MsnSlpLink *slplink; + + g_return_val_if_fail(session != NULL, NULL); + g_return_val_if_fail(username != NULL, NULL); + + slplink = msn_session_find_slplink(session, username); + + if (slplink == NULL) + slplink = msn_slplink_new(session, username); + + return slplink; +} + +MsnSlpSession * +msn_slplink_find_slp_session(MsnSlpLink *slplink, long session_id) +{ + GList *l; + MsnSlpSession *slpsession; + + for (l = slplink->slp_sessions; l != NULL; l = l->next) + { + slpsession = l->data; + + if (slpsession->id == session_id) + return slpsession; + } + + return NULL; +} + +void +msn_slplink_add_slpcall(MsnSlpLink *slplink, MsnSlpCall *slpcall) +{ + if (slplink->swboard != NULL) + slplink->swboard->flag |= MSN_SB_FLAG_FT; + + slplink->slp_calls = g_list_append(slplink->slp_calls, slpcall); +} + +void +msn_slplink_remove_slpcall(MsnSlpLink *slplink, MsnSlpCall *slpcall) +{ + slplink->slp_calls = g_list_remove(slplink->slp_calls, slpcall); + + /* The slplink has no slpcalls in it. If no one is using it, we might + * destroy the switchboard, but we should be careful not to use the slplink + * again. */ + if (slplink->slp_calls == NULL) + { + if (slplink->swboard != NULL) + { + if (msn_switchboard_release(slplink->swboard, MSN_SB_FLAG_FT)) + /* I'm not sure this is the best thing to do, but it's better + * than nothing. */ + slpcall->slplink = NULL; + } + } +} + +MsnSlpCall * +msn_slplink_find_slp_call(MsnSlpLink *slplink, const char *id) +{ + GList *l; + MsnSlpCall *slpcall; + + if (!id) + return NULL; + + for (l = slplink->slp_calls; l != NULL; l = l->next) + { + slpcall = l->data; + + if (slpcall->id && !strcmp(slpcall->id, id)) + return slpcall; + } + + return NULL; +} + +MsnSlpCall * +msn_slplink_find_slp_call_with_session_id(MsnSlpLink *slplink, long id) +{ + GList *l; + MsnSlpCall *slpcall; + + for (l = slplink->slp_calls; l != NULL; l = l->next) + { + slpcall = l->data; + + if (slpcall->session_id == id) + return slpcall; + } + + return NULL; +} + +void +msn_slplink_send_msg(MsnSlpLink *slplink, MsnMessage *msg) +{ + if (slplink->directconn != NULL) + { + msn_directconn_send_msg(slplink->directconn, msg); + } + else + { + if (slplink->swboard == NULL) + { + slplink->swboard = msn_session_get_swboard(slplink->session, + slplink->remote_user, MSN_SB_FLAG_FT); + + if (slplink->swboard == NULL) + return; + + /* If swboard is destroyed we will be too */ + slplink->swboard->slplinks = g_list_prepend(slplink->swboard->slplinks, slplink); + } + + msn_switchboard_send_msg(slplink->swboard, msg, TRUE); + } +} + +/* We have received the message ack */ +static void +msg_ack(MsnMessage *msg, void *data) +{ + MsnSlpMessage *slpmsg; + long long real_size; + + slpmsg = data; + + real_size = (slpmsg->flags == 0x2) ? 0 : slpmsg->size; + + slpmsg->offset += msg->msnslp_header.length; + + if (slpmsg->offset < real_size) + { + msn_slplink_send_msgpart(slpmsg->slplink, slpmsg); + } + else + { + /* The whole message has been sent */ + if (slpmsg->flags == 0x20 || slpmsg->flags == 0x1000030) + { + if (slpmsg->slpcall != NULL) + { + if (slpmsg->slpcall->cb) + slpmsg->slpcall->cb(slpmsg->slpcall, + NULL, 0); + } + } + } + + slpmsg->msgs = g_list_remove(slpmsg->msgs, msg); +} + +/* We have received the message nak. */ +static void +msg_nak(MsnMessage *msg, void *data) +{ + MsnSlpMessage *slpmsg; + + slpmsg = data; + + msn_slplink_send_msgpart(slpmsg->slplink, slpmsg); + + slpmsg->msgs = g_list_remove(slpmsg->msgs, msg); +} + +void +msn_slplink_send_msgpart(MsnSlpLink *slplink, MsnSlpMessage *slpmsg) +{ + MsnMessage *msg; + long long real_size; + size_t len = 0; + + /* Maybe we will want to create a new msg for this slpmsg instead of + * reusing the same one all the time. */ + msg = slpmsg->msg; + + real_size = (slpmsg->flags == 0x2) ? 0 : slpmsg->size; + + if (slpmsg->offset < real_size) + { + if (slpmsg->fp) + { + char data[1202]; + len = fread(data, 1, sizeof(data), slpmsg->fp); + msn_message_set_bin_data(msg, data, len); + } + else + { + len = slpmsg->size - slpmsg->offset; + + if (len > 1202) + len = 1202; + + msn_message_set_bin_data(msg, slpmsg->buffer + slpmsg->offset, len); + } + + msg->msnslp_header.offset = slpmsg->offset; + msg->msnslp_header.length = len; + } + +#ifdef MSN_DEBUG_SLP + msn_message_show_readable(msg, slpmsg->info, slpmsg->text_body); +#endif + +#ifdef MSN_DEBUG_SLP_FILES + debug_msg_to_file(msg, TRUE); +#endif + + slpmsg->msgs = + g_list_append(slpmsg->msgs, msg); + msn_slplink_send_msg(slplink, msg); + + if ((slpmsg->flags == 0x20 || slpmsg->flags == 0x1000030) && + (slpmsg->slpcall != NULL)) + { + slpmsg->slpcall->progress = TRUE; + + if (slpmsg->slpcall->progress_cb != NULL) + { + slpmsg->slpcall->progress_cb(slpmsg->slpcall, slpmsg->size, + len, slpmsg->offset); + } + } + + /* slpmsg->offset += len; */ +} + +void +msn_slplink_release_slpmsg(MsnSlpLink *slplink, MsnSlpMessage *slpmsg) +{ + MsnMessage *msg; + + slpmsg->msg = msg = msn_message_new_msnslp(); + + if (slpmsg->flags == 0x0) + { + msg->msnslp_header.session_id = slpmsg->session_id; + msg->msnslp_header.ack_id = rand() % 0xFFFFFF00; + } + else if (slpmsg->flags == 0x2) + { + msg->msnslp_header.session_id = slpmsg->session_id; + msg->msnslp_header.ack_id = slpmsg->ack_id; + msg->msnslp_header.ack_size = slpmsg->ack_size; + msg->msnslp_header.ack_sub_id = slpmsg->ack_sub_id; + } + else if (slpmsg->flags == 0x20 || slpmsg->flags == 0x1000030) + { + MsnSlpSession *slpsession; + slpsession = slpmsg->slpsession; + + g_return_if_fail(slpsession != NULL); + msg->msnslp_header.session_id = slpsession->id; + msg->msnslp_footer.value = slpsession->app_id; + msg->msnslp_header.ack_id = rand() % 0xFFFFFF00; + } + else if (slpmsg->flags == 0x100) + { + msg->msnslp_header.ack_id = slpmsg->ack_id; + msg->msnslp_header.ack_sub_id = slpmsg->ack_sub_id; + msg->msnslp_header.ack_size = slpmsg->ack_size; + } + + msg->msnslp_header.id = slpmsg->id; + msg->msnslp_header.flags = slpmsg->flags; + + msg->msnslp_header.total_size = slpmsg->size; + + msn_message_set_attr(msg, "P2P-Dest", slplink->remote_user); + + msg->ack_cb = msg_ack; + msg->nak_cb = msg_nak; + msg->ack_data = slpmsg; + + msn_slplink_send_msgpart(slplink, slpmsg); + + msn_message_destroy(msg); +} + +void +msn_slplink_queue_slpmsg(MsnSlpLink *slplink, MsnSlpMessage *slpmsg) +{ + slpmsg->id = slplink->slp_seq_id++; + + g_queue_push_head(slplink->slp_msg_queue, slpmsg); +} + +void +msn_slplink_send_slpmsg(MsnSlpLink *slplink, MsnSlpMessage *slpmsg) +{ + slpmsg->id = slplink->slp_seq_id++; + + msn_slplink_release_slpmsg(slplink, slpmsg); +} + +void +msn_slplink_unleash(MsnSlpLink *slplink) +{ + MsnSlpMessage *slpmsg; + + /* Send the queued msgs in the order they came. */ + + while ((slpmsg = g_queue_pop_tail(slplink->slp_msg_queue)) != NULL) + { + msn_slplink_release_slpmsg(slplink, slpmsg); + } +} + +void +msn_slplink_send_ack(MsnSlpLink *slplink, MsnMessage *msg) +{ + MsnSlpMessage *slpmsg; + + slpmsg = msn_slpmsg_new(slplink); + + slpmsg->session_id = msg->msnslp_header.session_id; + slpmsg->size = msg->msnslp_header.total_size; + slpmsg->flags = 0x02; + slpmsg->ack_id = msg->msnslp_header.id; + slpmsg->ack_sub_id = msg->msnslp_header.ack_id; + slpmsg->ack_size = msg->msnslp_header.total_size; + +#ifdef MSN_DEBUG_SLP + slpmsg->info = "SLP ACK"; +#endif + + msn_slplink_send_slpmsg(slplink, slpmsg); +} + +static void +send_file_cb(MsnSlpSession *slpsession) +{ + MsnSlpCall *slpcall; + MsnSlpMessage *slpmsg; + struct stat st; + PurpleXfer *xfer; + + slpcall = slpsession->slpcall; + slpmsg = msn_slpmsg_new(slpcall->slplink); + slpmsg->slpcall = slpcall; + slpmsg->flags = 0x1000030; + slpmsg->slpsession = slpsession; +#ifdef MSN_DEBUG_SLP + slpmsg->info = "SLP FILE"; +#endif + xfer = (PurpleXfer *)slpcall->xfer; + purple_xfer_start(slpcall->xfer, 0, NULL, 0); + slpmsg->fp = xfer->dest_fp; + if (g_stat(purple_xfer_get_local_filename(xfer), &st) == 0) + slpmsg->size = st.st_size; + xfer->dest_fp = NULL; /* Disable double fclose() */ + + msn_slplink_send_slpmsg(slpcall->slplink, slpmsg); +} + +void +msn_slplink_process_msg(MsnSlpLink *slplink, MsnMessage *msg) +{ + MsnSlpMessage *slpmsg; + const char *data; + gsize offset; + gsize len; + +#ifdef MSN_DEBUG_SLP + msn_slpmsg_show(msg); +#endif + +#ifdef MSN_DEBUG_SLP_FILES + debug_msg_to_file(msg, FALSE); +#endif + + if (msg->msnslp_header.total_size < msg->msnslp_header.length) + { + purple_debug_error("msn", "This can't be good\n"); + g_return_if_reached(); + } + + slpmsg = NULL; + data = msn_message_get_bin_data(msg, &len); + + /* + OVERHEAD! + if (msg->msnslp_header.length < msg->msnslp_header.total_size) + */ + + offset = msg->msnslp_header.offset; + + if (offset == 0) + { + slpmsg = msn_slpmsg_new(slplink); + slpmsg->id = msg->msnslp_header.id; + slpmsg->session_id = msg->msnslp_header.session_id; + slpmsg->size = msg->msnslp_header.total_size; + slpmsg->flags = msg->msnslp_header.flags; + + if (slpmsg->session_id) + { + if (slpmsg->slpcall == NULL) + slpmsg->slpcall = msn_slplink_find_slp_call_with_session_id(slplink, slpmsg->session_id); + + if (slpmsg->slpcall != NULL) + { + if (slpmsg->flags == 0x20 || slpmsg->flags == 0x1000030) + { + PurpleXfer *xfer; + + xfer = slpmsg->slpcall->xfer; + + if (xfer != NULL) + { + purple_xfer_start(slpmsg->slpcall->xfer, + 0, NULL, 0); + slpmsg->fp = ((PurpleXfer *)slpmsg->slpcall->xfer)->dest_fp; + xfer->dest_fp = NULL; /* Disable double fclose() */ + } + } + } + } + if (!slpmsg->fp && slpmsg->size) + { + slpmsg->buffer = g_try_malloc(slpmsg->size); + if (slpmsg->buffer == NULL) + { + purple_debug_error("msn", "Failed to allocate buffer for slpmsg\n"); + return; + } + } + } + else + { + slpmsg = msn_slplink_message_find(slplink, msg->msnslp_header.session_id, msg->msnslp_header.id); + } + + if (slpmsg == NULL) + { + /* Probably the transfer was canceled */ + purple_debug_error("msn", "Couldn't find slpmsg\n"); + return; + } + + if (slpmsg->fp) + { + /* fseek(slpmsg->fp, offset, SEEK_SET); */ + len = fwrite(data, 1, len, slpmsg->fp); + } + else if (slpmsg->size) + { + if ((offset + len) > slpmsg->size) + { + purple_debug_error("msn", "Oversized slpmsg\n"); + g_return_if_reached(); + } + else + memcpy(slpmsg->buffer + offset, data, len); + } + + if ((slpmsg->flags == 0x20 || slpmsg->flags == 0x1000030) && + (slpmsg->slpcall != NULL)) + { + slpmsg->slpcall->progress = TRUE; + + if (slpmsg->slpcall->progress_cb != NULL) + { + slpmsg->slpcall->progress_cb(slpmsg->slpcall, slpmsg->size, + len, offset); + } + } + +#if 0 + if (slpmsg->buffer == NULL) + return; +#endif + + if (msg->msnslp_header.offset + msg->msnslp_header.length + >= msg->msnslp_header.total_size) + { + /* All the pieces of the slpmsg have been received */ + MsnSlpCall *slpcall; + + slpcall = msn_slp_process_msg(slplink, slpmsg); + + if (slpmsg->flags == 0x100) + { + MsnDirectConn *directconn; + + directconn = slplink->directconn; + + if (!directconn->acked) + msn_directconn_send_handshake(directconn); + } + else if (slpmsg->flags == 0x0 || slpmsg->flags == 0x20 || + slpmsg->flags == 0x1000030) + { + /* Release all the messages and send the ACK */ + + msn_slplink_send_ack(slplink, msg); + msn_slplink_unleash(slplink); + } + + msn_slpmsg_destroy(slpmsg); + + if (slpcall != NULL && slpcall->wasted) + msn_slp_call_destroy(slpcall); + } +} + +MsnSlpMessage * +msn_slplink_message_find(MsnSlpLink *slplink, long session_id, long id) +{ + GList *e; + + for (e = slplink->slp_msgs; e != NULL; e = e->next) + { + MsnSlpMessage *slpmsg = e->data; + + if ((slpmsg->session_id == session_id) && (slpmsg->id == id)) + return slpmsg; + } + + return NULL; +} + +typedef struct +{ + guint32 length; + guint32 unk1; + guint32 file_size; + guint32 unk2; + guint32 unk3; +} MsnContextHeader; + +#define MAX_FILE_NAME_LEN 0x226 + +static gchar * +gen_context(const char *file_name, const char *file_path) +{ + struct stat st; + gsize size = 0; + MsnContextHeader header; + gchar *u8 = NULL; + guchar *base; + guchar *n; + gchar *ret; + gunichar2 *uni = NULL; + glong currentChar = 0; + glong uni_len = 0; + gsize len; + + if (g_stat(file_path, &st) == 0) + size = st.st_size; + + if(!file_name) { + u8 = purple_utf8_try_convert(g_basename(file_path)); + file_name = u8; + } + + uni = g_utf8_to_utf16(file_name, -1, NULL, &uni_len, NULL); + + if(u8) { + g_free(u8); + file_name = NULL; + u8 = NULL; + } + + len = sizeof(MsnContextHeader) + MAX_FILE_NAME_LEN + 4; + + header.length = GUINT32_TO_LE(len); + header.unk1 = GUINT32_TO_LE(2); + header.file_size = GUINT32_TO_LE(size); + header.unk2 = GUINT32_TO_LE(0); + header.unk3 = GUINT32_TO_LE(0); + + base = g_malloc(len + 1); + n = base; + + memcpy(n, &header, sizeof(MsnContextHeader)); + n += sizeof(MsnContextHeader); + + memset(n, 0x00, MAX_FILE_NAME_LEN); + for(currentChar = 0; currentChar < uni_len; currentChar++) { + *((gunichar2 *)n + currentChar) = GUINT16_TO_LE(uni[currentChar]); + } + n += MAX_FILE_NAME_LEN; + + memset(n, 0xFF, 4); + n += 4; + + g_free(uni); + ret = purple_base64_encode(base, len); + g_free(base); + return ret; +} + +void +msn_slplink_request_ft(MsnSlpLink *slplink, PurpleXfer *xfer) +{ + MsnSlpCall *slpcall; + char *context; + const char *fn; + const char *fp; + + fn = purple_xfer_get_filename(xfer); + fp = purple_xfer_get_local_filename(xfer); + + g_return_if_fail(slplink != NULL); + g_return_if_fail(fp != NULL); + + slpcall = msn_slp_call_new(slplink); + msn_slp_call_init(slpcall, MSN_SLPCALL_DC); + + slpcall->session_init_cb = send_file_cb; + slpcall->end_cb = msn_xfer_end_cb; + slpcall->progress_cb = msn_xfer_progress_cb; + slpcall->cb = msn_xfer_completed_cb; + slpcall->xfer = xfer; + purple_xfer_ref(slpcall->xfer); + + slpcall->pending = TRUE; + + purple_xfer_set_cancel_send_fnc(xfer, msn_xfer_cancel); + + xfer->data = slpcall; + + context = gen_context(fn, fp); + + msn_slp_call_invite(slpcall, "5D3E02AB-6190-11D3-BBBB-00C04F795683", 2, + context); + + g_free(context); +} + +void +msn_slplink_request_object(MsnSlpLink *slplink, + const char *info, + MsnSlpCb cb, + MsnSlpEndCb end_cb, + const MsnObject *obj) +{ + MsnSlpCall *slpcall; + char *msnobj_data; + char *msnobj_base64; + + g_return_if_fail(slplink != NULL); + g_return_if_fail(obj != NULL); + + msnobj_data = msn_object_to_string(obj); + msnobj_base64 = purple_base64_encode((const guchar *)msnobj_data, strlen(msnobj_data)); + g_free(msnobj_data); + + slpcall = msn_slp_call_new(slplink); + msn_slp_call_init(slpcall, MSN_SLPCALL_ANY); + + slpcall->data_info = g_strdup(info); + slpcall->cb = cb; + slpcall->end_cb = end_cb; + + msn_slp_call_invite(slpcall, "A4268EEC-FEC5-49E5-95C3-F126696BDBF6", 1, + msnobj_base64); + + g_free(msnobj_base64); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/slplink.h Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,97 @@ +/** + * @file slplink.h MSNSLP Link support + * + * 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 _MSN_SLPLINK_H_ +#define _MSN_SLPLINK_H_ + +typedef struct _MsnSlpLink MsnSlpLink; + +#include "directconn.h" +#include "slpcall.h" +#include "slpmsg.h" + +#include "switchboard.h" + +#include "ft.h" + +#include "session.h" + +typedef void (*MsnSlpCb)(MsnSlpCall *slpcall, + const guchar *data, gsize size); +typedef void (*MsnSlpEndCb)(MsnSlpCall *slpcall, MsnSession *session); + +struct _MsnSlpLink +{ + MsnSession *session; + MsnSwitchBoard *swboard; + + char *local_user; + char *remote_user; + + int slp_seq_id; + + MsnDirectConn *directconn; + + GList *slp_calls; + GList *slp_sessions; + GList *slp_msgs; + + GQueue *slp_msg_queue; +}; + +MsnSlpLink *msn_slplink_new(MsnSession *session, const char *username); +void msn_slplink_destroy(MsnSlpLink *slplink); +MsnSlpLink *msn_session_find_slplink(MsnSession *session, + const char *who); +MsnSlpLink *msn_session_get_slplink(MsnSession *session, const char *username); +MsnSlpSession *msn_slplink_find_slp_session(MsnSlpLink *slplink, + long session_id); +void msn_slplink_add_slpcall(MsnSlpLink *slplink, MsnSlpCall *slpcall); +void msn_slplink_remove_slpcall(MsnSlpLink *slplink, MsnSlpCall *slpcall); +MsnSlpCall *msn_slplink_find_slp_call(MsnSlpLink *slplink, + const char *id); +MsnSlpCall *msn_slplink_find_slp_call_with_session_id(MsnSlpLink *slplink, long id); +void msn_slplink_send_msg(MsnSlpLink *slplink, MsnMessage *msg); +void msn_slplink_release_slpmsg(MsnSlpLink *slplink, + MsnSlpMessage *slpmsg); +void msn_slplink_queue_slpmsg(MsnSlpLink *slplink, MsnSlpMessage *slpmsg); +void msn_slplink_send_slpmsg(MsnSlpLink *slplink, + MsnSlpMessage *slpmsg); +void msn_slplink_unleash(MsnSlpLink *slplink); +void msn_slplink_send_ack(MsnSlpLink *slplink, MsnMessage *msg); +void msn_slplink_process_msg(MsnSlpLink *slplink, MsnMessage *msg); +MsnSlpMessage *msn_slplink_message_find(MsnSlpLink *slplink, long session_id, long id); +void msn_slplink_append_slp_msg(MsnSlpLink *slplink, MsnSlpMessage *slpmsg); +void msn_slplink_remove_slp_msg(MsnSlpLink *slplink, + MsnSlpMessage *slpmsg); +void msn_slplink_request_ft(MsnSlpLink *slplink, PurpleXfer *xfer); + +void msn_slplink_request_object(MsnSlpLink *slplink, + const char *info, + MsnSlpCb cb, + MsnSlpEndCb end_cb, + const MsnObject *obj); + +MsnSlpCall *msn_slp_process_msg(MsnSlpLink *slplink, MsnSlpMessage *slpmsg); + +#endif /* _MSN_SLPLINK_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/slpmsg.c Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,239 @@ +/** + * @file slpmsg.h SLP Message functions + * + * 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 "msn.h" +#include "slpmsg.h" +#include "slplink.h" + +/************************************************************************** + * SLP Message + **************************************************************************/ + +MsnSlpMessage * +msn_slpmsg_new(MsnSlpLink *slplink) +{ + MsnSlpMessage *slpmsg; + + slpmsg = g_new0(MsnSlpMessage, 1); + +#ifdef MSN_DEBUG_SLPMSG + purple_debug_info("msn", "slpmsg new (%p)\n", slpmsg); +#endif + + slpmsg->slplink = slplink; + + slplink->slp_msgs = + g_list_append(slplink->slp_msgs, slpmsg); + + return slpmsg; +} + +void +msn_slpmsg_destroy(MsnSlpMessage *slpmsg) +{ + MsnSlpLink *slplink; + GList *cur; + + g_return_if_fail(slpmsg != NULL); + +#ifdef MSN_DEBUG_SLPMSG + purple_debug_info("msn", "slpmsg destroy (%p)\n", slpmsg); +#endif + + slplink = slpmsg->slplink; + + if (slpmsg->fp != NULL) + fclose(slpmsg->fp); + + purple_imgstore_unref(slpmsg->img); + + /* We don't want to free the data of the PurpleStoredImage, + * but to avoid code duplication, it's sharing buffer. */ + if (slpmsg->img == NULL) + g_free(slpmsg->buffer); + +#ifdef MSN_DEBUG_SLP + /* + if (slpmsg->info != NULL) + g_free(slpmsg->info); + */ +#endif + + for (cur = slpmsg->msgs; cur != NULL; cur = cur->next) + { + /* Something is pointing to this slpmsg, so we should remove that + * pointer to prevent a crash. */ + /* Ex: a user goes offline and after that we receive an ACK */ + + MsnMessage *msg = cur->data; + +#ifdef MSN_DEBUG_SLPMSG + purple_debug_info("msn", "Unlink slpmsg callbacks.\n"); +#endif + + msg->ack_cb = NULL; + msg->nak_cb = NULL; + msg->ack_data = NULL; + } + + slplink->slp_msgs = g_list_remove(slplink->slp_msgs, slpmsg); + + g_free(slpmsg); +} + +void +msn_slpmsg_set_body(MsnSlpMessage *slpmsg, const char *body, + long long size) +{ + /* We can only have one data source at a time. */ + g_return_if_fail(slpmsg->buffer == NULL); + g_return_if_fail(slpmsg->img == NULL); + g_return_if_fail(slpmsg->fp == NULL); + + if (body != NULL) + slpmsg->buffer = g_memdup(body, size); + else + slpmsg->buffer = g_new0(guchar, size); + + slpmsg->size = size; +} + +void +msn_slpmsg_set_image(MsnSlpMessage *slpmsg, PurpleStoredImage *img) +{ + /* We can only have one data source at a time. */ + g_return_if_fail(slpmsg->buffer == NULL); + g_return_if_fail(slpmsg->img == NULL); + g_return_if_fail(slpmsg->fp == NULL); + + slpmsg->img = purple_imgstore_ref(img); + slpmsg->buffer = (guchar *)purple_imgstore_get_data(img); + slpmsg->size = purple_imgstore_get_size(img); +} + +void +msn_slpmsg_open_file(MsnSlpMessage *slpmsg, const char *file_name) +{ + struct stat st; + + /* We can only have one data source at a time. */ + g_return_if_fail(slpmsg->buffer == NULL); + g_return_if_fail(slpmsg->img == NULL); + g_return_if_fail(slpmsg->fp == NULL); + + slpmsg->fp = g_fopen(file_name, "rb"); + + if (g_stat(file_name, &st) == 0) + slpmsg->size = st.st_size; +} + +#ifdef MSN_DEBUG_SLP +void +msn_slpmsg_show(MsnMessage *msg) +{ + const char *info; + gboolean text; + guint32 flags; + + text = FALSE; + + flags = GUINT32_TO_LE(msg->msnslp_header.flags); + + switch (flags) + { + case 0x0: + info = "SLP CONTROL"; + text = TRUE; + break; + case 0x2: + info = "SLP ACK"; break; + case 0x20: + case 0x1000030: + info = "SLP DATA"; break; + default: + info = "SLP UNKNOWN"; break; + } + + msn_message_show_readable(msg, info, text); +} +#endif + +MsnSlpMessage * +msn_slpmsg_sip_new(MsnSlpCall *slpcall, int cseq, + const char *header, const char *branch, + const char *content_type, const char *content) +{ + MsnSlpLink *slplink; + MsnSlpMessage *slpmsg; + char *body; + gsize body_len; + gsize content_len; + + g_return_val_if_fail(slpcall != NULL, NULL); + g_return_val_if_fail(header != NULL, NULL); + + slplink = slpcall->slplink; + + /* Let's remember that "content" should end with a 0x00 */ + + content_len = (content != NULL) ? strlen(content) + 1 : 0; + + body = g_strdup_printf( + "%s\r\n" + "To: <msnmsgr:%s>\r\n" + "From: <msnmsgr:%s>\r\n" + "Via: MSNSLP/1.0/TLP ;branch={%s}\r\n" + "CSeq: %d\r\n" + "Call-ID: {%s}\r\n" + "Max-Forwards: 0\r\n" + "Content-Type: %s\r\n" + "Content-Length: %" G_GSIZE_FORMAT "\r\n" + "\r\n", + header, + slplink->remote_user, + slplink->local_user, + branch, + cseq, + slpcall->id, + content_type, + content_len); + + body_len = strlen(body); + + if (content_len > 0) + { + body_len += content_len; + body = g_realloc(body, body_len); + g_strlcat(body, content, body_len); + } + + slpmsg = msn_slpmsg_new(slplink); + msn_slpmsg_set_body(slpmsg, body, body_len); + + slpmsg->sip = TRUE; + slpmsg->slpcall = slpcall; + + g_free(body); + + return slpmsg; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/slpmsg.h Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,109 @@ +/** + * @file slpmsg.h SLP Message functions + * + * 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 _MSN_SLPMSG_H_ +#define _MSN_SLPMSG_H_ + +typedef struct _MsnSlpMessage MsnSlpMessage; + +#include "imgstore.h" + +#include "slpsession.h" +#include "slpcall.h" +#include "slplink.h" +#include "session.h" +#include "msg.h" + +#include "slp.h" + +/** + * A SLP Message This contains everything that we will need to send a SLP + * Message even if has to be sent in several parts. + */ +struct _MsnSlpMessage +{ + MsnSlpSession *slpsession; + MsnSlpCall *slpcall; /**< The slpcall to which this slp message belongs (if applicable). */ + MsnSlpLink *slplink; /**< The slplink through which this slp message is being sent. */ + MsnSession *session; + + long session_id; + long id; + long ack_id; + long ack_sub_id; + long long ack_size; + long app_id; + + gboolean sip; /**< A flag that states if this is a SIP slp message. */ + int ref_count; /**< The reference count. */ + long flags; + + FILE *fp; + PurpleStoredImage *img; + guchar *buffer; + long long offset; + long long size; + + GList *msgs; /**< The real messages. */ + +#if 1 + MsnMessage *msg; /**< The temporary real message that will be sent. */ +#endif + +#ifdef MSN_DEBUG_SLP + char *info; + gboolean text_body; +#endif +}; + +/** + * Creates a new slp message + * + * @param slplink The slplink through which this slp message will be sent. + * @return The created slp message. + */ +MsnSlpMessage *msn_slpmsg_new(MsnSlpLink *slplink); + +/** + * Destroys a slp message + * + * @param slpmsg The slp message to destory. + */ +void msn_slpmsg_destroy(MsnSlpMessage *slpmsg); + +void msn_slpmsg_set_body(MsnSlpMessage *slpmsg, const char *body, + long long size); +void msn_slpmsg_set_image(MsnSlpMessage *slpmsg, PurpleStoredImage *img); +void msn_slpmsg_open_file(MsnSlpMessage *slpmsg, + const char *file_name); +MsnSlpMessage * msn_slpmsg_sip_new(MsnSlpCall *slpcall, int cseq, + const char *header, + const char *branch, + const char *content_type, + const char *content); + +#ifdef MSN_DEBUG_SLP +void msn_slpmsg_show(MsnMessage *msg); +#endif + +#endif /* _MSN_SLPMSG_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/slpsession.c Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,77 @@ +/** + * @file slpsession.h SLP Session functions + * + * 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 "slpsession.h" + +/************************************************************************** + * SLP Session + **************************************************************************/ + +MsnSlpSession * +msn_slp_session_new(MsnSlpCall *slpcall) +{ + MsnSlpSession *slpsession; + + g_return_val_if_fail(slpcall != NULL, NULL); + + slpsession = g_new0(MsnSlpSession, 1); + + slpsession->slpcall = slpcall; + slpsession->id = slpcall->session_id; + slpsession->call_id = slpcall->id; + slpsession->app_id = slpcall->app_id; + + slpcall->slplink->slp_sessions = + g_list_append(slpcall->slplink->slp_sessions, slpsession); + + return slpsession; +} + +void +msn_slp_session_destroy(MsnSlpSession *slpsession) +{ + g_return_if_fail(slpsession != NULL); + + if (slpsession->call_id != NULL) + g_free(slpsession->call_id); + + slpsession->slpcall->slplink->slp_sessions = + g_list_remove(slpsession->slpcall->slplink->slp_sessions, slpsession); + + g_free(slpsession); +} + +#if 0 +static void +msn_slp_session_send_slpmsg(MsnSlpSession *slpsession, MsnSlpMessage *slpmsg) +{ + slpmsg->slpsession = slpsession; + +#if 0 + slpmsg->session_id = slpsession->id; + slpmsg->app_id = slpsession->app_id; +#endif + + msn_slplink_send_slpmsg(slpsession->slpcall->slplink, slpmsg); +} +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/slpsession.h Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,48 @@ +/** + * @file slpsession.h SLP Session functions + * + * 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 _MSN_SLPSESSION_H_ +#define _MSN_SLPSESSION_H_ + +typedef struct _MsnSlpSession MsnSlpSession; + +#include "slpcall.h" +#include "slpsession.h" +#include "slpmsg.h" + +struct _MsnSlpSession +{ + /* MsnSlpLink *slplink; */ + MsnSlpCall *slpcall; + + long id; + + long app_id; + char *call_id; +}; + +MsnSlpSession *msn_slp_session_new(MsnSlpCall *slpcall); +void msn_slp_session_destroy(MsnSlpSession *slpsession); +void msn_slpsession_send_slpmsg(MsnSlpSession *slpsession, + MsnSlpMessage *slpmsg); +#endif /* _MSN_SLPSESSION_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/state.c Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,132 @@ +/** + * @file state.c State functions and definitions + * + * 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 "msn.h" +#include "state.h" + +static const char *away_text[] = +{ + N_("Available"), + N_("Available"), + N_("Busy"), + N_("Idle"), + N_("Be Right Back"), + N_("Away From Computer"), + N_("On The Phone"), + N_("Out To Lunch"), + N_("Available"), + N_("Available") +}; + +void +msn_change_status(MsnSession *session) +{ + PurpleAccount *account; + MsnCmdProc *cmdproc; + MsnUser *user; + MsnObject *msnobj; + const char *state_text; + + g_return_if_fail(session != NULL); + g_return_if_fail(session->notification != NULL); + + account = session->account; + cmdproc = session->notification->cmdproc; + user = session->user; + state_text = msn_state_get_text(msn_state_from_account(account)); + + /* If we're not logged in yet, don't send the status to the server, + * it will be sent when login completes + */ + if (!session->logged_in) + return; + + msnobj = msn_user_get_object(user); + + if (msnobj == NULL) + { + msn_cmdproc_send(cmdproc, "CHG", "%s %d", state_text, + MSN_CLIENT_ID); + } + else + { + char *msnobj_str; + + msnobj_str = msn_object_to_string(msnobj); + + msn_cmdproc_send(cmdproc, "CHG", "%s %d %s", state_text, + MSN_CLIENT_ID, purple_url_encode(msnobj_str)); + + g_free(msnobj_str); + } +} + +const char * +msn_away_get_text(MsnAwayType type) +{ + g_return_val_if_fail(type <= MSN_HIDDEN, NULL); + + return _(away_text[type]); +} + +const char * +msn_state_get_text(MsnAwayType state) +{ + static char *status_text[] = + { "NLN", "NLN", "BSY", "IDL", "BRB", "AWY", "PHN", "LUN", "HDN", "HDN" }; + + return status_text[state]; +} + +MsnAwayType +msn_state_from_account(PurpleAccount *account) +{ + MsnAwayType msnstatus; + PurplePresence *presence; + PurpleStatus *status; + const char *status_id; + + presence = purple_account_get_presence(account); + status = purple_presence_get_active_status(presence); + status_id = purple_status_get_id(status); + + if (!strcmp(status_id, "away")) + msnstatus = MSN_AWAY; + else if (!strcmp(status_id, "brb")) + msnstatus = MSN_BRB; + else if (!strcmp(status_id, "busy")) + msnstatus = MSN_BUSY; + else if (!strcmp(status_id, "phone")) + msnstatus = MSN_PHONE; + else if (!strcmp(status_id, "lunch")) + msnstatus = MSN_LUNCH; + else if (!strcmp(status_id, "invisible")) + msnstatus = MSN_HIDDEN; + else + msnstatus = MSN_ONLINE; + + if ((msnstatus == MSN_ONLINE) && purple_presence_is_idle(presence)) + msnstatus = MSN_IDLE; + + return msnstatus; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/state.h Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,64 @@ +/** + * @file state.h State functions and definitions + * + * 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 _MSN_STATE_H_ +#define _MSN_STATE_H_ + +/** + * Away types. + */ +typedef enum +{ + MSN_ONLINE = 1, + MSN_BUSY = 2, + MSN_IDLE = 3, + MSN_BRB = 4, + MSN_AWAY = 5, + MSN_PHONE = 6, + MSN_LUNCH = 7, + MSN_OFFLINE = 8, + MSN_HIDDEN = 9 + +} MsnAwayType; + +/** + * Changes the status of the user. + * + * @param session The MSN session. + */ +void msn_change_status(MsnSession *session); + +/** + * Returns the string representation of an away type. + * + * @param type The away type. + * + * @return The string representation of the away type. + */ +const char *msn_away_get_text(MsnAwayType type); + +const char *msn_state_get_text(MsnAwayType state); + +MsnAwayType msn_state_from_account(PurpleAccount *account); + +#endif /* _MSN_STATE_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/switchboard.c Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,1294 @@ +/** + * @file switchboard.c MSN switchboard functions + * + * 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 "msn.h" +#include "prefs.h" +#include "switchboard.h" +#include "notification.h" +#include "msn-utils.h" + +#include "error.h" + +static MsnTable *cbs_table; + +static void msg_error_helper(MsnCmdProc *cmdproc, MsnMessage *msg, + MsnMsgErrorType error); + +/************************************************************************** + * Main + **************************************************************************/ + +MsnSwitchBoard * +msn_switchboard_new(MsnSession *session) +{ + MsnSwitchBoard *swboard; + MsnServConn *servconn; + + g_return_val_if_fail(session != NULL, NULL); + + swboard = g_new0(MsnSwitchBoard, 1); + + swboard->session = session; + swboard->servconn = servconn = msn_servconn_new(session, MSN_SERVCONN_SB); + swboard->cmdproc = servconn->cmdproc; + + swboard->msg_queue = g_queue_new(); + swboard->empty = TRUE; + + swboard->cmdproc->data = swboard; + swboard->cmdproc->cbs_table = cbs_table; + + session->switches = g_list_append(session->switches, swboard); + + return swboard; +} + +void +msn_switchboard_destroy(MsnSwitchBoard *swboard) +{ + MsnSession *session; + MsnMessage *msg; + GList *l; + +#ifdef MSN_DEBUG_SB + purple_debug_info("msn", "switchboard_destroy: swboard(%p)\n", swboard); +#endif + + g_return_if_fail(swboard != NULL); + + if (swboard->destroying) + return; + + swboard->destroying = TRUE; + + /* If it linked us is because its looking for trouble */ + while (swboard->slplinks != NULL) + msn_slplink_destroy(swboard->slplinks->data); + + /* Destroy the message queue */ + while ((msg = g_queue_pop_head(swboard->msg_queue)) != NULL) + { + if (swboard->error != MSN_SB_ERROR_NONE) + { + /* The messages could not be sent due to a switchboard error */ + msg_error_helper(swboard->cmdproc, msg, + MSN_MSG_ERROR_SB); + } + msn_message_unref(msg); + } + + g_queue_free(swboard->msg_queue); + + /* msg_error_helper will both remove the msg from ack_list and + unref it, so we don't need to do either here */ + while ((l = swboard->ack_list) != NULL) + msg_error_helper(swboard->cmdproc, l->data, MSN_MSG_ERROR_SB); + + g_free(swboard->im_user); + g_free(swboard->auth_key); + g_free(swboard->session_id); + + for (l = swboard->users; l != NULL; l = l->next) + g_free(l->data); + + session = swboard->session; + session->switches = g_list_remove(session->switches, swboard); + +#if 0 + /* This should never happen or we are in trouble. */ + if (swboard->servconn != NULL) + msn_servconn_destroy(swboard->servconn); +#endif + + swboard->cmdproc->data = NULL; + + msn_servconn_set_disconnect_cb(swboard->servconn, NULL); + + msn_servconn_destroy(swboard->servconn); + + g_free(swboard); +} + +void +msn_switchboard_set_auth_key(MsnSwitchBoard *swboard, const char *key) +{ + g_return_if_fail(swboard != NULL); + g_return_if_fail(key != NULL); + + swboard->auth_key = g_strdup(key); +} + +const char * +msn_switchboard_get_auth_key(MsnSwitchBoard *swboard) +{ + g_return_val_if_fail(swboard != NULL, NULL); + + return swboard->auth_key; +} + +void +msn_switchboard_set_session_id(MsnSwitchBoard *swboard, const char *id) +{ + g_return_if_fail(swboard != NULL); + g_return_if_fail(id != NULL); + + if (swboard->session_id != NULL) + g_free(swboard->session_id); + + swboard->session_id = g_strdup(id); +} + +const char * +msn_switchboard_get_session_id(MsnSwitchBoard *swboard) +{ + g_return_val_if_fail(swboard != NULL, NULL); + + return swboard->session_id; +} + +void +msn_switchboard_set_invited(MsnSwitchBoard *swboard, gboolean invited) +{ + g_return_if_fail(swboard != NULL); + + swboard->invited = invited; +} + +gboolean +msn_switchboard_is_invited(MsnSwitchBoard *swboard) +{ + g_return_val_if_fail(swboard != NULL, FALSE); + + return swboard->invited; +} + +/************************************************************************** + * Utility + **************************************************************************/ + +static void +send_clientcaps(MsnSwitchBoard *swboard) +{ + MsnMessage *msg; + + msg = msn_message_new(MSN_MSG_CAPS); + msn_message_set_content_type(msg, "text/x-clientcaps"); + msn_message_set_flag(msg, 'U'); + msn_message_set_bin_data(msg, MSN_CLIENTINFO, strlen(MSN_CLIENTINFO)); + + msn_switchboard_send_msg(swboard, msg, TRUE); + + msn_message_destroy(msg); +} + +static void +msn_switchboard_add_user(MsnSwitchBoard *swboard, const char *user) +{ + MsnCmdProc *cmdproc; + PurpleAccount *account; + + g_return_if_fail(swboard != NULL); + + cmdproc = swboard->cmdproc; + account = cmdproc->session->account; + + swboard->users = g_list_prepend(swboard->users, g_strdup(user)); + swboard->current_users++; + swboard->empty = FALSE; + +#ifdef MSN_DEBUG_CHAT + purple_debug_info("msn", "user=[%s], total=%d\n", user, + swboard->current_users); +#endif + + if (!(swboard->flag & MSN_SB_FLAG_IM) && (swboard->conv != NULL)) + { + /* This is a helper switchboard. */ + purple_debug_error("msn", "switchboard_add_user: conv != NULL\n"); + return; + } + + if ((swboard->conv != NULL) && + (purple_conversation_get_type(swboard->conv) == PURPLE_CONV_TYPE_CHAT)) + { + purple_conv_chat_add_user(PURPLE_CONV_CHAT(swboard->conv), user, NULL, + PURPLE_CBFLAGS_NONE, TRUE); + } + else if (swboard->current_users > 1 || swboard->total_users > 1) + { + if (swboard->conv == NULL || + purple_conversation_get_type(swboard->conv) != PURPLE_CONV_TYPE_CHAT) + { + GList *l; + +#ifdef MSN_DEBUG_CHAT + purple_debug_info("msn", "[chat] Switching to chat.\n"); +#endif + +#if 0 + /* this is bad - it causes msn_switchboard_close to be called on the + * switchboard we're in the middle of using :( */ + if (swboard->conv != NULL) + purple_conversation_destroy(swboard->conv); +#endif + + swboard->chat_id = cmdproc->session->conv_seq++; + swboard->flag |= MSN_SB_FLAG_IM; + swboard->conv = serv_got_joined_chat(account->gc, + swboard->chat_id, + "MSN Chat"); + + for (l = swboard->users; l != NULL; l = l->next) + { + const char *tmp_user; + + tmp_user = l->data; + +#ifdef MSN_DEBUG_CHAT + purple_debug_info("msn", "[chat] Adding [%s].\n", tmp_user); +#endif + + purple_conv_chat_add_user(PURPLE_CONV_CHAT(swboard->conv), + tmp_user, NULL, PURPLE_CBFLAGS_NONE, TRUE); + } + +#ifdef MSN_DEBUG_CHAT + purple_debug_info("msn", "[chat] We add ourselves.\n"); +#endif + + purple_conv_chat_add_user(PURPLE_CONV_CHAT(swboard->conv), + purple_account_get_username(account), + NULL, PURPLE_CBFLAGS_NONE, TRUE); + + g_free(swboard->im_user); + swboard->im_user = NULL; + } + } + else if (swboard->conv == NULL) + { + swboard->conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, + user, account); + } + else + { + purple_debug_warning("msn", "switchboard_add_user: This should not happen!\n"); + } +} + +static PurpleConversation * +msn_switchboard_get_conv(MsnSwitchBoard *swboard) +{ + PurpleAccount *account; + + g_return_val_if_fail(swboard != NULL, NULL); + + if (swboard->conv != NULL) + return swboard->conv; + + purple_debug_error("msn", "Switchboard with unassigned conversation\n"); + + account = swboard->session->account; + + return (swboard->conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, + account, swboard->im_user)); +} + +static void +msn_switchboard_report_user(MsnSwitchBoard *swboard, PurpleMessageFlags flags, const char *msg) +{ + PurpleConversation *conv; + + g_return_if_fail(swboard != NULL); + g_return_if_fail(msg != NULL); + + if ((conv = msn_switchboard_get_conv(swboard)) != NULL) + { + purple_conversation_write(conv, NULL, msg, flags, time(NULL)); + } +} + +static void +swboard_error_helper(MsnSwitchBoard *swboard, int reason, const char *passport) +{ + g_return_if_fail(swboard != NULL); + + purple_debug_warning("msg", "Error: Unable to call the user %s for reason %i\n", + passport ? passport : "(null)", reason); + + /* TODO: if current_users > 0, this is probably a chat and an invite failed, + * we should report that in the chat or something */ + if (swboard->current_users == 0) + { + swboard->error = reason; + msn_switchboard_close(swboard); + } +} + +static void +cal_error_helper(MsnTransaction *trans, int reason) +{ + MsnSwitchBoard *swboard; + const char *passport; + char **params; + + params = g_strsplit(trans->params, " ", 0); + + passport = params[0]; + + swboard = trans->data; + + purple_debug_warning("msn", "cal_error_helper: command %s failed for reason %i\n",trans->command,reason); + + swboard_error_helper(swboard, reason, passport); + + g_strfreev(params); +} + +static void +msg_error_helper(MsnCmdProc *cmdproc, MsnMessage *msg, MsnMsgErrorType error) +{ + MsnSwitchBoard *swboard; + + g_return_if_fail(cmdproc != NULL); + g_return_if_fail(msg != NULL); + + if ((error != MSN_MSG_ERROR_SB) && (msg->nak_cb != NULL)) + msg->nak_cb(msg, msg->ack_data); + + swboard = cmdproc->data; + + /* This is not good, and should be fixed somewhere else. */ + g_return_if_fail(swboard != NULL); + + if (msg->type == MSN_MSG_TEXT) + { + const char *format, *str_reason; + char *body_str, *body_enc, *pre, *post; + +#if 0 + if (swboard->conv == NULL) + { + if (msg->ack_ref) + msn_message_unref(msg); + + return; + } +#endif + + if (error == MSN_MSG_ERROR_TIMEOUT) + { + str_reason = _("Message may have not been sent " + "because a timeout occurred:"); + } + else if (error == MSN_MSG_ERROR_SB) + { + switch (swboard->error) + { + case MSN_SB_ERROR_OFFLINE: + str_reason = _("Message could not be sent, " + "not allowed while invisible:"); + break; + case MSN_SB_ERROR_USER_OFFLINE: + str_reason = _("Message could not be sent " + "because the user is offline:"); + break; + case MSN_SB_ERROR_CONNECTION: + str_reason = _("Message could not be sent " + "because a connection error occurred:"); + break; + case MSN_SB_ERROR_TOO_FAST: + str_reason = _("Message could not be sent " + "because we are sending too quickly:"); + break; + case MSN_SB_ERROR_AUTHFAILED: + str_reason = _("Message could not be sent " + "because we were unable to establish a " + "session with the server. This is " + "likely a server problem, try again in " + "a few minutes:"); + break; + default: + str_reason = _("Message could not be sent " + "because an error with " + "the switchboard occurred:"); + break; + } + } + else + { + str_reason = _("Message may have not been sent " + "because an unknown error occurred:"); + } + + body_str = msn_message_to_string(msg); + body_enc = g_markup_escape_text(body_str, -1); + g_free(body_str); + + format = msn_message_get_attr(msg, "X-MMS-IM-Format"); + msn_parse_format(format, &pre, &post); + body_str = g_strdup_printf("%s%s%s", pre ? pre : "", + body_enc ? body_enc : "", post ? post : ""); + g_free(body_enc); + g_free(pre); + g_free(post); + + msn_switchboard_report_user(swboard, PURPLE_MESSAGE_ERROR, + str_reason); + msn_switchboard_report_user(swboard, PURPLE_MESSAGE_RAW, + body_str); + + g_free(body_str); + } + + /* If a timeout occures we will want the msg around just in case we + * receive the ACK after the timeout. */ + if (msg->ack_ref && error != MSN_MSG_ERROR_TIMEOUT) + { + swboard->ack_list = g_list_remove(swboard->ack_list, msg); + msn_message_unref(msg); + } +} + +/************************************************************************** + * Message Stuff + **************************************************************************/ + +/** Called when a message times out. */ +static void +msg_timeout(MsnCmdProc *cmdproc, MsnTransaction *trans) +{ + MsnMessage *msg; + + msg = trans->data; + + msg_error_helper(cmdproc, msg, MSN_MSG_ERROR_TIMEOUT); +} + +/** Called when we receive an error of a message. */ +static void +msg_error(MsnCmdProc *cmdproc, MsnTransaction *trans, int error) +{ + msg_error_helper(cmdproc, trans->data, MSN_MSG_ERROR_UNKNOWN); +} + +#if 0 +/** Called when we receive an ack of a special message. */ +static void +msg_ack(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnMessage *msg; + + msg = cmd->trans->data; + + if (msg->ack_cb != NULL) + msg->ack_cb(msg->ack_data); + + msn_message_unref(msg); +} + +/** Called when we receive a nak of a special message. */ +static void +msg_nak(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnMessage *msg; + + msg = cmd->trans->data; + + msn_message_unref(msg); +} +#endif + +static void +release_msg(MsnSwitchBoard *swboard, MsnMessage *msg) +{ + MsnCmdProc *cmdproc; + MsnTransaction *trans; + char *payload; + gsize payload_len; + + g_return_if_fail(swboard != NULL); + g_return_if_fail(msg != NULL); + + cmdproc = swboard->cmdproc; + + payload = msn_message_gen_payload(msg, &payload_len); + +#ifdef MSN_DEBUG_SB + msn_message_show_readable(msg, "SB SEND", FALSE); +#endif + + trans = msn_transaction_new(cmdproc, "MSG", "%c %d", + msn_message_get_flag(msg), payload_len); + + /* Data for callbacks */ + msn_transaction_set_data(trans, msg); + + if (msg->type == MSN_MSG_TEXT) + { + msg->ack_ref = TRUE; + msn_message_ref(msg); + swboard->ack_list = g_list_append(swboard->ack_list, msg); + msn_transaction_set_timeout_cb(trans, msg_timeout); + } + else if (msg->type == MSN_MSG_SLP) + { + msg->ack_ref = TRUE; + msn_message_ref(msg); + swboard->ack_list = g_list_append(swboard->ack_list, msg); + msn_transaction_set_timeout_cb(trans, msg_timeout); +#if 0 + if (msg->ack_cb != NULL) + { + msn_transaction_add_cb(trans, "ACK", msg_ack); + msn_transaction_add_cb(trans, "NAK", msg_nak); + } +#endif + } + + trans->payload = payload; + trans->payload_len = payload_len; + + msg->trans = trans; + + msn_cmdproc_send_trans(cmdproc, trans); +} + +static void +queue_msg(MsnSwitchBoard *swboard, MsnMessage *msg) +{ + g_return_if_fail(swboard != NULL); + g_return_if_fail(msg != NULL); + + purple_debug_info("msn", "Appending message to queue.\n"); + + g_queue_push_tail(swboard->msg_queue, msg); + + msn_message_ref(msg); +} + +static void +process_queue(MsnSwitchBoard *swboard) +{ + MsnMessage *msg; + + g_return_if_fail(swboard != NULL); + + purple_debug_info("msn", "Processing queue\n"); + + while ((msg = g_queue_pop_head(swboard->msg_queue)) != NULL) + { + purple_debug_info("msn", "Sending message\n"); + release_msg(swboard, msg); + msn_message_unref(msg); + } +} + +gboolean +msn_switchboard_can_send(MsnSwitchBoard *swboard) +{ + g_return_val_if_fail(swboard != NULL, FALSE); + + if (swboard->empty || !g_queue_is_empty(swboard->msg_queue)) + return FALSE; + + return TRUE; +} + +void +msn_switchboard_send_msg(MsnSwitchBoard *swboard, MsnMessage *msg, + gboolean queue) +{ + g_return_if_fail(swboard != NULL); + g_return_if_fail(msg != NULL); + + if (msn_switchboard_can_send(swboard)) + release_msg(swboard, msg); + else if (queue) + queue_msg(swboard, msg); +} + +/************************************************************************** + * Switchboard Commands + **************************************************************************/ + +static void +ans_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnSwitchBoard *swboard; + + swboard = cmdproc->data; + swboard->ready = TRUE; +} + +static void +bye_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnSwitchBoard *swboard; + const char *user; + + swboard = cmdproc->data; + user = cmd->params[0]; + + /* cmdproc->data is set to NULL when the switchboard is destroyed; + * we may get a bye shortly thereafter. */ + g_return_if_fail(swboard != NULL); + + if (!(swboard->flag & MSN_SB_FLAG_IM) && (swboard->conv != NULL)) + purple_debug_error("msn_switchboard", "bye_cmd: helper bug\n"); + + if (swboard->conv == NULL) + { + /* This is a helper switchboard */ + msn_switchboard_destroy(swboard); + } + else if ((swboard->current_users > 1) || + (purple_conversation_get_type(swboard->conv) == PURPLE_CONV_TYPE_CHAT)) + { + /* This is a switchboard used for a chat */ + purple_conv_chat_remove_user(PURPLE_CONV_CHAT(swboard->conv), user, NULL); + swboard->current_users--; + if (swboard->current_users == 0) + msn_switchboard_destroy(swboard); + } + else + { + /* This is a switchboard used for a im session */ + msn_switchboard_destroy(swboard); + } +} + +static void +iro_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + PurpleAccount *account; + PurpleConnection *gc; + MsnSwitchBoard *swboard; + + account = cmdproc->session->account; + gc = account->gc; + swboard = cmdproc->data; + + swboard->total_users = atoi(cmd->params[2]); + + msn_switchboard_add_user(swboard, cmd->params[3]); +} + +static void +joi_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnSession *session; + PurpleAccount *account; + PurpleConnection *gc; + MsnSwitchBoard *swboard; + const char *passport; + + passport = cmd->params[0]; + + session = cmdproc->session; + account = session->account; + gc = account->gc; + swboard = cmdproc->data; + + msn_switchboard_add_user(swboard, passport); + + process_queue(swboard); + + if (!session->http_method) + send_clientcaps(swboard); + + if (swboard->closed) + msn_switchboard_close(swboard); +} + +static void +msg_cmd_post(MsnCmdProc *cmdproc, MsnCommand *cmd, char *payload, size_t len) +{ + MsnMessage *msg; + + msg = msn_message_new_from_cmd(cmdproc->session, cmd); + + msn_message_parse_payload(msg, payload, len); +#ifdef MSN_DEBUG_SB + msn_message_show_readable(msg, "SB RECV", FALSE); +#endif + + if (msg->remote_user != NULL) + g_free (msg->remote_user); + + msg->remote_user = g_strdup(cmd->params[0]); + msn_cmdproc_process_msg(cmdproc, msg); + + msn_message_destroy(msg); +} + +static void +msg_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + cmdproc->servconn->payload_len = atoi(cmd->params[2]); + cmdproc->last_cmd->payload_cb = msg_cmd_post; +} + +static void +nak_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnMessage *msg; + + msg = cmd->trans->data; + g_return_if_fail(msg != NULL); + + msg_error_helper(cmdproc, msg, MSN_MSG_ERROR_NAK); +} + +static void +ack_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnSwitchBoard *swboard; + MsnMessage *msg; + + msg = cmd->trans->data; + + if (msg->ack_cb != NULL) + msg->ack_cb(msg, msg->ack_data); + + swboard = cmdproc->data; + if (swboard) + swboard->ack_list = g_list_remove(swboard->ack_list, msg); + msn_message_unref(msg); +} + +static void +out_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + PurpleConnection *gc; + MsnSwitchBoard *swboard; + + gc = cmdproc->session->account->gc; + swboard = cmdproc->data; + + if (swboard->current_users > 1) + serv_got_chat_left(gc, swboard->chat_id); + + msn_switchboard_disconnect(swboard); +} + +static void +usr_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnSwitchBoard *swboard; + + swboard = cmdproc->data; + +#if 0 + GList *l; + + for (l = swboard->users; l != NULL; l = l->next) + { + const char *user; + user = l->data; + + msn_cmdproc_send(cmdproc, "CAL", "%s", user); + } +#endif + + swboard->ready = TRUE; + msn_cmdproc_process_queue(cmdproc); +} + +/************************************************************************** + * Message Handlers + **************************************************************************/ +static void +plain_msg(MsnCmdProc *cmdproc, MsnMessage *msg) +{ + PurpleConnection *gc; + MsnSwitchBoard *swboard; + const char *body; + char *body_str; + char *body_enc; + char *body_final; + size_t body_len; + const char *passport; + const char *value; + + gc = cmdproc->session->account->gc; + swboard = cmdproc->data; + + body = msn_message_get_bin_data(msg, &body_len); + body_str = g_strndup(body, body_len); + body_enc = g_markup_escape_text(body_str, -1); + g_free(body_str); + + passport = msg->remote_user; + + if (!strcmp(passport, "messenger@microsoft.com") && + strstr(body, "immediate security update")) + { + return; + } + +#if 0 + if ((value = msn_message_get_attr(msg, "User-Agent")) != NULL) + { + purple_debug_misc("msn", "User-Agent = '%s'\n", value); + } +#endif + + if ((value = msn_message_get_attr(msg, "X-MMS-IM-Format")) != NULL) + { + char *pre, *post; + + msn_parse_format(value, &pre, &post); + + body_final = g_strdup_printf("%s%s%s", pre ? pre : "", + body_enc ? body_enc : "", post ? post : ""); + + g_free(pre); + g_free(post); + g_free(body_enc); + } + else + { + body_final = body_enc; + } + + swboard->flag |= MSN_SB_FLAG_IM; + + if (swboard->current_users > 1 || + ((swboard->conv != NULL) && + purple_conversation_get_type(swboard->conv) == PURPLE_CONV_TYPE_CHAT)) + { + /* If current_users is always ok as it should then there is no need to + * check if this is a chat. */ + if (swboard->current_users <= 1) + purple_debug_misc("msn", "plain_msg: current_users(%d)\n", + swboard->current_users); + + serv_got_chat_in(gc, swboard->chat_id, passport, 0, body_final, + time(NULL)); + if (swboard->conv == NULL) + { + swboard->conv = purple_find_chat(gc, swboard->chat_id); + swboard->flag |= MSN_SB_FLAG_IM; + } + } + else + { + serv_got_im(gc, passport, body_final, 0, time(NULL)); + if (swboard->conv == NULL) + { + swboard->conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, + passport, purple_connection_get_account(gc)); + swboard->flag |= MSN_SB_FLAG_IM; + } + } + + g_free(body_final); +} + +static void +control_msg(MsnCmdProc *cmdproc, MsnMessage *msg) +{ + PurpleConnection *gc; + MsnSwitchBoard *swboard; + char *passport; + + gc = cmdproc->session->account->gc; + swboard = cmdproc->data; + passport = msg->remote_user; + + if (swboard->current_users == 1 && + msn_message_get_attr(msg, "TypingUser") != NULL) + { + serv_got_typing(gc, passport, MSN_TYPING_RECV_TIMEOUT, + PURPLE_TYPING); + } +} + +static void +clientcaps_msg(MsnCmdProc *cmdproc, MsnMessage *msg) +{ +#if 0 + MsnSession *session; + MsnSwitchBoard *swboard; + MsnUser *user; + GHashTable *clientcaps; + const char *value; + + char *passport = msg->sender; + + session = cmdproc->session; + swboard = cmdproc->servconn->swboard; + + clientcaps = msn_message_get_hashtable_from_body(msg); +#endif +} + +static void +nudge_msg(MsnCmdProc *cmdproc, MsnMessage *msg) +{ + MsnSwitchBoard *swboard; + PurpleAccount *account; + const char *user; + + swboard = cmdproc->data; + account = cmdproc->session->account; + user = msg->remote_user; + + serv_got_attention(account->gc, user, MSN_NUDGE); +} + +/************************************************************************** + * Connect stuff + **************************************************************************/ +static void +ans_usr_error(MsnCmdProc *cmdproc, MsnTransaction *trans, int error); + +static void +connect_cb(MsnServConn *servconn) +{ + MsnSwitchBoard *swboard; + MsnTransaction *trans; + MsnCmdProc *cmdproc; + PurpleAccount *account; + + cmdproc = servconn->cmdproc; + g_return_if_fail(cmdproc != NULL); + + account = cmdproc->session->account; + swboard = cmdproc->data; + g_return_if_fail(swboard != NULL); + + if (msn_switchboard_is_invited(swboard)) + { + swboard->empty = FALSE; + + trans = msn_transaction_new(cmdproc, "ANS", "%s %s %s", + purple_account_get_username(account), + swboard->auth_key, swboard->session_id); + } + else + { + trans = msn_transaction_new(cmdproc, "USR", "%s %s", + purple_account_get_username(account), + swboard->auth_key); + } + + msn_transaction_set_error_cb(trans, ans_usr_error); + msn_transaction_set_data(trans, swboard); + msn_cmdproc_send_trans(cmdproc, trans); +} + +static void +ans_usr_error(MsnCmdProc *cmdproc, MsnTransaction *trans, int error) +{ + MsnSwitchBoard *swboard; + char **params; + char *passport; + int reason = MSN_SB_ERROR_UNKNOWN; + + if (error == 911) + { + reason = MSN_SB_ERROR_AUTHFAILED; + } + + purple_debug_warning("msn", "ans_usr_error: command %s gave error %i\n", trans->command, error); + + params = g_strsplit(trans->params, " ", 0); + passport = params[0]; + swboard = trans->data; + + swboard_error_helper(swboard, reason, passport); + + g_strfreev(params); +} + +static void +disconnect_cb(MsnServConn *servconn) +{ + MsnSwitchBoard *swboard; + + swboard = servconn->cmdproc->data; + g_return_if_fail(swboard != NULL); + + msn_servconn_set_disconnect_cb(swboard->servconn, NULL); + + msn_switchboard_destroy(swboard); +} + +gboolean +msn_switchboard_connect(MsnSwitchBoard *swboard, const char *host, int port) +{ + g_return_val_if_fail(swboard != NULL, FALSE); + + msn_servconn_set_connect_cb(swboard->servconn, connect_cb); + msn_servconn_set_disconnect_cb(swboard->servconn, disconnect_cb); + + return msn_servconn_connect(swboard->servconn, host, port); +} + +void +msn_switchboard_disconnect(MsnSwitchBoard *swboard) +{ + g_return_if_fail(swboard != NULL); + + msn_servconn_disconnect(swboard->servconn); +} + +/************************************************************************** + * Call stuff + **************************************************************************/ +static void +got_cal(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ +#if 0 + MsnSwitchBoard *swboard; + const char *user; + + swboard = cmdproc->data; + + user = cmd->params[0]; + + msn_switchboard_add_user(swboard, user); +#endif +} + +static void +cal_timeout(MsnCmdProc *cmdproc, MsnTransaction *trans) +{ + purple_debug_warning("msn", "cal_timeout: command %s timed out\n", trans->command); + + cal_error_helper(trans, MSN_SB_ERROR_UNKNOWN); +} + +static void +cal_error(MsnCmdProc *cmdproc, MsnTransaction *trans, int error) +{ + int reason = MSN_SB_ERROR_UNKNOWN; + + if (error == 215) + { + purple_debug_info("msn", "Invited user already in switchboard\n"); + return; + } + else if (error == 217) + { + reason = MSN_SB_ERROR_USER_OFFLINE; + } + + purple_debug_warning("msn", "cal_error: command %s gave error %i\n", trans->command, error); + + cal_error_helper(trans, reason); +} + +void +msn_switchboard_request_add_user(MsnSwitchBoard *swboard, const char *user) +{ + MsnTransaction *trans; + MsnCmdProc *cmdproc; + + g_return_if_fail(swboard != NULL); + + cmdproc = swboard->cmdproc; + + trans = msn_transaction_new(cmdproc, "CAL", "%s", user); + /* this doesn't do anything, but users seem to think that + * 'Unhandled command' is some kind of error, so we don't report it */ + msn_transaction_add_cb(trans, "CAL", got_cal); + + msn_transaction_set_data(trans, swboard); + msn_transaction_set_timeout_cb(trans, cal_timeout); + + if (swboard->ready) + msn_cmdproc_send_trans(cmdproc, trans); + else + msn_cmdproc_queue_trans(cmdproc, trans); +} + +/************************************************************************** + * Create & Transfer stuff + **************************************************************************/ + +static void +got_swboard(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnSwitchBoard *swboard; + char *host; + int port; + swboard = cmd->trans->data; + + if (g_list_find(cmdproc->session->switches, swboard) == NULL) + /* The conversation window was closed. */ + return; + + msn_switchboard_set_auth_key(swboard, cmd->params[4]); + + msn_parse_socket(cmd->params[2], &host, &port); + + if (!msn_switchboard_connect(swboard, host, port)) + msn_switchboard_destroy(swboard); + + g_free(host); +} + +static void +xfr_error(MsnCmdProc *cmdproc, MsnTransaction *trans, int error) +{ + MsnSwitchBoard *swboard; + int reason = MSN_SB_ERROR_UNKNOWN; + + if (error == 913) + reason = MSN_SB_ERROR_OFFLINE; + else if (error == 800) + reason = MSN_SB_ERROR_TOO_FAST; + + swboard = trans->data; + + purple_debug_info("msn", "xfr_error %i for %s: trans %x, command %s, reason %i\n", + error, (swboard->im_user ? swboard->im_user : "(null)"), trans, + (trans->command ? trans->command : "(null)"), reason); + + swboard_error_helper(swboard, reason, swboard->im_user); +} + +void +msn_switchboard_request(MsnSwitchBoard *swboard) +{ + MsnCmdProc *cmdproc; + MsnTransaction *trans; + + g_return_if_fail(swboard != NULL); + + cmdproc = swboard->session->notification->cmdproc; + + trans = msn_transaction_new(cmdproc, "XFR", "%s", "SB"); + msn_transaction_add_cb(trans, "XFR", got_swboard); + + msn_transaction_set_data(trans, swboard); + msn_transaction_set_error_cb(trans, xfr_error); + + msn_cmdproc_send_trans(cmdproc, trans); +} + +void +msn_switchboard_close(MsnSwitchBoard *swboard) +{ + g_return_if_fail(swboard != NULL); + + if (swboard->error != MSN_SB_ERROR_NONE) + { + msn_switchboard_destroy(swboard); + } + else if (g_queue_is_empty(swboard->msg_queue) || + !swboard->session->connected) + { + MsnCmdProc *cmdproc; + cmdproc = swboard->cmdproc; + msn_cmdproc_send_quick(cmdproc, "OUT", NULL, NULL); + + msn_switchboard_destroy(swboard); + } + else + { + swboard->closed = TRUE; + } +} + +gboolean +msn_switchboard_release(MsnSwitchBoard *swboard, MsnSBFlag flag) +{ + g_return_val_if_fail(swboard != NULL, FALSE); + + swboard->flag &= ~flag; + + if (flag == MSN_SB_FLAG_IM) + /* Forget any conversation that used to be associated with this + * swboard. */ + swboard->conv = NULL; + + if (swboard->flag == 0) + { + msn_switchboard_close(swboard); + return TRUE; + } + + return FALSE; +} + +/************************************************************************** + * Init stuff + **************************************************************************/ + +void +msn_switchboard_init(void) +{ + cbs_table = msn_table_new(); + + msn_table_add_cmd(cbs_table, "ANS", "ANS", ans_cmd); + msn_table_add_cmd(cbs_table, "ANS", "IRO", iro_cmd); + + msn_table_add_cmd(cbs_table, "MSG", "ACK", ack_cmd); + msn_table_add_cmd(cbs_table, "MSG", "NAK", nak_cmd); + + msn_table_add_cmd(cbs_table, "USR", "USR", usr_cmd); + + msn_table_add_cmd(cbs_table, NULL, "MSG", msg_cmd); + msn_table_add_cmd(cbs_table, NULL, "JOI", joi_cmd); + msn_table_add_cmd(cbs_table, NULL, "BYE", bye_cmd); + msn_table_add_cmd(cbs_table, NULL, "OUT", out_cmd); + +#if 0 + /* They might skip the history */ + msn_table_add_cmd(cbs_table, NULL, "ACK", NULL); +#endif + + msn_table_add_error(cbs_table, "MSG", msg_error); + msn_table_add_error(cbs_table, "CAL", cal_error); + + /* Register the message type callbacks. */ + msn_table_add_msg_type(cbs_table, "text/plain", + plain_msg); + msn_table_add_msg_type(cbs_table, "text/x-msmsgscontrol", + control_msg); + msn_table_add_msg_type(cbs_table, "text/x-clientcaps", + clientcaps_msg); + msn_table_add_msg_type(cbs_table, "text/x-clientinfo", + clientcaps_msg); + msn_table_add_msg_type(cbs_table, "application/x-msnmsgrp2p", + msn_p2p_msg); + msn_table_add_msg_type(cbs_table, "text/x-mms-emoticon", + msn_emoticon_msg); + msn_table_add_msg_type(cbs_table, "text/x-mms-animemoticon", + msn_emoticon_msg); + msn_table_add_msg_type(cbs_table, "text/x-msnmsgr-datacast", + nudge_msg); +#if 0 + msn_table_add_msg_type(cbs_table, "text/x-msmmsginvite", + msn_invite_msg); +#endif +} + +void +msn_switchboard_end(void) +{ + msn_table_destroy(cbs_table); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/switchboard.h Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,277 @@ +/** + * @file switchboard.h MSN switchboard functions + * + * 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 _MSN_SWITCHBOARD_H_ +#define _MSN_SWITCHBOARD_H_ + +typedef struct _MsnSwitchBoard MsnSwitchBoard; + +#include "conversation.h" + +#include "msg.h" +#include "user.h" + +#include "servconn.h" + +#include "slplink.h" + +/** + * A switchboard error. + */ +typedef enum +{ + MSN_SB_ERROR_NONE, /**< No error. */ + MSN_SB_ERROR_CAL, /**< The user could not join (answer the call). */ + MSN_SB_ERROR_OFFLINE, /**< The account is offline. */ + MSN_SB_ERROR_USER_OFFLINE, /**< The user to call is offline. */ + MSN_SB_ERROR_CONNECTION, /**< There was a connection error. */ + MSN_SB_ERROR_TOO_FAST, /**< We are sending too fast */ + MSN_SB_ERROR_AUTHFAILED, /**< Authentication failed joining the switchboard session */ + MSN_SB_ERROR_UNKNOWN /**< An unknown error occurred. */ + +} MsnSBErrorType; + +/** + * A switchboard flag. + */ +typedef enum +{ + MSN_SB_FLAG_IM = 0x01, /**< This switchboard is being used for a conversation. */ + MSN_SB_FLAG_FT = 0x02, /**< This switchboard is being used for file transfer. */ + +} MsnSBFlag; + +/** + * A switchboard. + * + * A place where a bunch of users send messages to the rest of the users. + */ +struct _MsnSwitchBoard +{ + MsnSession *session; + MsnServConn *servconn; + MsnCmdProc *cmdproc; + char *im_user; + + MsnSBFlag flag; + char *auth_key; + char *session_id; + + PurpleConversation *conv; /**< The conversation that displays the + messages of this switchboard, or @c NULL if + this is a helper switchboard. */ + + gboolean empty; /**< A flag that states if the swithcboard has no + users in it. */ + gboolean invited; /**< A flag that states if we were invited to the + switchboard. */ + gboolean ready; /**< A flag that states if this switchboard is + ready to be used. */ + gboolean closed; /**< A flag that states if the switchboard has + been closed by the user. */ + gboolean destroying; /**< A flag that states if the switchboard is + alredy on the process of destruction. */ + + int current_users; + int total_users; + GList *users; + + int chat_id; + + GQueue *msg_queue; /**< Queue of messages to send. */ + GList *ack_list; /**< List of messages waiting for an ack. */ + + MsnSBErrorType error; /**< The error that occurred in this switchboard + (if applicable). */ + GList *slplinks; /**< The list of slplinks that are using this switchboard. */ +}; + +/** + * Initialize the variables for switchboard creation. + */ +void msn_switchboard_init(void); + +/** + * Destroy the variables for switchboard creation. + */ +void msn_switchboard_end(void); + +/** + * Creates a new switchboard. + * + * @param session The MSN session. + * + * @return The new switchboard. + */ +MsnSwitchBoard *msn_switchboard_new(MsnSession *session); + +/** + * Destroys a switchboard. + * + * @param swboard The switchboard to destroy. + */ +void msn_switchboard_destroy(MsnSwitchBoard *swboard); + +/** + * Sets the auth key the switchboard must use when connecting. + * + * @param swboard The switchboard. + * @param key The auth key. + */ +void msn_switchboard_set_auth_key(MsnSwitchBoard *swboard, const char *key); + +/** + * Returns the auth key the switchboard must use when connecting. + * + * @param swboard The switchboard. + * + * @return The auth key. + */ +const char *msn_switchboard_get_auth_key(MsnSwitchBoard *swboard); + +/** + * Sets the session ID the switchboard must use when connecting. + * + * @param swboard The switchboard. + * @param id The session ID. + */ +void msn_switchboard_set_session_id(MsnSwitchBoard *swboard, const char *id); + +/** + * Returns the session ID the switchboard must use when connecting. + * + * @param swboard The switchboard. + * + * @return The session ID. + */ +const char *msn_switchboard_get_session_id(MsnSwitchBoard *swboard); + +/** + * Sets whether or not we were invited to this switchboard. + * + * @param swboard The switchboard. + * @param invite @c TRUE if invited, @c FALSE otherwise. + */ +void msn_switchboard_set_invited(MsnSwitchBoard *swboard, gboolean invited); + +/** + * Returns whether or not we were invited to this switchboard. + * + * @param swboard The switchboard. + * + * @return @c TRUE if invited, @c FALSE otherwise. + */ +gboolean msn_switchboard_is_invited(MsnSwitchBoard *swboard); + +/** + * Connects to a switchboard. + * + * @param swboard The switchboard. + * @param host The switchboard server host. + * @param port The switcbharod server port. + * + * @return @c TRUE if able to connect, or @c FALSE otherwise. + */ +gboolean msn_switchboard_connect(MsnSwitchBoard *swboard, + const char *host, int port); + +/** + * Disconnects from a switchboard. + * + * @param swboard The switchboard to disconnect from. + */ +void msn_switchboard_disconnect(MsnSwitchBoard *swboard); + +/** + * Closes the switchboard. + * + * Called when a conversation is closed. + * + * @param swboard The switchboard to close. + */ +void msn_switchboard_close(MsnSwitchBoard *swboard); + +/** + * Release a switchboard from a certain function. + * + * @param swboard The switchboard to release. + * @param flag The flag that states the function. + * + * @return @c TRUE if the switchboard was closed, @c FALSE otherwise. + */ +gboolean msn_switchboard_release(MsnSwitchBoard *swboard, MsnSBFlag flag); + +/** + * Returns whether or not we currently can send a message through this + * switchboard. + * + * @param swboard The switchboard. + * + * @return @c TRUE if a message can be sent, @c FALSE otherwise. + */ +gboolean msn_switchboard_can_send(MsnSwitchBoard *swboard); + +/** + * Sends a message through this switchboard. + * + * @param swboard The switchboard. + * @param msg The message. + * @param queue A flag that states if we want this message to be queued (in + * the case it cannot currently be sent). + * + * @return @c TRUE if a message can be sent, @c FALSE otherwise. + */ +void msn_switchboard_send_msg(MsnSwitchBoard *swboard, MsnMessage *msg, + gboolean queue); + +gboolean msn_switchboard_chat_leave(MsnSwitchBoard *swboard); +gboolean msn_switchboard_chat_invite(MsnSwitchBoard *swboard, const char *who); + +void msn_switchboard_request(MsnSwitchBoard *swboard); +void msn_switchboard_request_add_user(MsnSwitchBoard *swboard, const char *user); + +/** + * Processes peer to peer messages. + * + * @param cmdproc The command processor. + * @param msg The message. + */ +void msn_p2p_msg(MsnCmdProc *cmdproc, MsnMessage *msg); + +/** + * Processes emoticon messages. + * + * @param cmdproc The command processor. + * @param msg The message. + */ +void msn_emoticon_msg(MsnCmdProc *cmdproc, MsnMessage *msg); + +/** + * Processes INVITE messages. + * + * @param cmdproc The command processor. + * @param msg The message. + */ +void msn_invite_msg(MsnCmdProc *cmdproc, MsnMessage *msg); + +#endif /* _MSN_SWITCHBOARD_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/sync.c Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,258 @@ +/** + * @file sync.c MSN list synchronization functions + * + * 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 "msn.h" +#include "sync.h" +#include "state.h" + +static MsnTable *cbs_table; + +static void +blp_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + PurpleConnection *gc = cmdproc->session->account->gc; + const char *list_name; + + list_name = cmd->params[0]; + + if (!g_ascii_strcasecmp(list_name, "AL")) + { + /* + * If the current setting is AL, messages from users who + * are not in BL will be delivered. + * + * In other words, deny some. + */ + gc->account->perm_deny = PURPLE_PRIVACY_DENY_USERS; + } + else + { + /* If the current setting is BL, only messages from people + * who are in the AL will be delivered. + * + * In other words, permit some. + */ + gc->account->perm_deny = PURPLE_PRIVACY_ALLOW_USERS; + } +} + +static void +prp_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnSession *session = cmdproc->session; + const char *type, *value; + + type = cmd->params[0]; + value = cmd->params[1]; + + if (cmd->param_count == 2) + { + if (!strcmp(type, "PHH")) + msn_user_set_home_phone(session->user, purple_url_decode(value)); + else if (!strcmp(type, "PHW")) + msn_user_set_work_phone(session->user, purple_url_decode(value)); + else if (!strcmp(type, "PHM")) + msn_user_set_mobile_phone(session->user, purple_url_decode(value)); + } + else + { + if (!strcmp(type, "PHH")) + msn_user_set_home_phone(session->user, NULL); + else if (!strcmp(type, "PHW")) + msn_user_set_work_phone(session->user, NULL); + else if (!strcmp(type, "PHM")) + msn_user_set_mobile_phone(session->user, NULL); + } +} + +static void +lsg_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnSession *session = cmdproc->session; + const char *name; + int group_id; + + group_id = atoi(cmd->params[0]); + name = purple_url_decode(cmd->params[1]); + + msn_group_new(session->userlist, group_id, name); + + /* HACK */ + if (group_id == 0) + { + /* Group of ungroupped buddies */ + if (session->sync->total_users == 0) + { + cmdproc->cbs_table = session->sync->old_cbs_table; + + msn_session_finish_login(session); + + msn_sync_destroy(session->sync); + session->sync = NULL; + } + return; + } + + if ((purple_find_group(name)) == NULL) + { + PurpleGroup *g = purple_group_new(name); + purple_blist_add_group(g, NULL); + } +} + +static void +lst_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnSession *session = cmdproc->session; + char *passport = NULL; + const char *friend = NULL; + int list_op; + MsnUser *user; + + passport = cmd->params[0]; + friend = purple_url_decode(cmd->params[1]); + list_op = atoi(cmd->params[2]); + + user = msn_user_new(session->userlist, passport, friend); + + msn_userlist_add_user(session->userlist, user); + + session->sync->last_user = user; + + /* TODO: This can be improved */ + + if (list_op & MSN_LIST_FL_OP) + { + char **c; + char **tokens; + const char *group_nums; + GSList *group_ids; + + group_nums = cmd->params[3]; + + group_ids = NULL; + + tokens = g_strsplit(group_nums, ",", -1); + + for (c = tokens; *c != NULL; c++) + { + int id; + + id = atoi(*c); + group_ids = g_slist_append(group_ids, GINT_TO_POINTER(id)); + } + + g_strfreev(tokens); + + msn_got_lst_user(session, user, list_op, group_ids); + + g_slist_free(group_ids); + } + else + { + msn_got_lst_user(session, user, list_op, NULL); + } + + session->sync->num_users++; + + if (session->sync->num_users == session->sync->total_users) + { + cmdproc->cbs_table = session->sync->old_cbs_table; + + msn_session_finish_login(session); + + msn_sync_destroy(session->sync); + session->sync = NULL; + } +} + +static void +bpr_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnSync *sync = cmdproc->session->sync; + const char *type, *value; + MsnUser *user; + + user = sync->last_user; + + g_return_if_fail(user != NULL); + + type = cmd->params[0]; + value = cmd->params[1]; + + if (value) + { + if (!strcmp(type, "MOB")) + { + if (!strcmp(value, "Y")) + user->mobile = TRUE; + } + else if (!strcmp(type, "PHH")) + msn_user_set_home_phone(user, purple_url_decode(value)); + else if (!strcmp(type, "PHW")) + msn_user_set_work_phone(user, purple_url_decode(value)); + else if (!strcmp(type, "PHM")) + msn_user_set_mobile_phone(user, purple_url_decode(value)); + } +} + +void +msn_sync_init(void) +{ + /* TODO: check prp, blp, bpr */ + + cbs_table = msn_table_new(); + + /* Syncing */ + msn_table_add_cmd(cbs_table, NULL, "GTC", NULL); + msn_table_add_cmd(cbs_table, NULL, "BLP", blp_cmd); + msn_table_add_cmd(cbs_table, NULL, "PRP", prp_cmd); + msn_table_add_cmd(cbs_table, NULL, "LSG", lsg_cmd); + msn_table_add_cmd(cbs_table, NULL, "LST", lst_cmd); + msn_table_add_cmd(cbs_table, NULL, "BPR", bpr_cmd); +} + +void +msn_sync_end(void) +{ + msn_table_destroy(cbs_table); +} + +MsnSync * +msn_sync_new(MsnSession *session) +{ + MsnSync *sync; + + sync = g_new0(MsnSync, 1); + + sync->session = session; + sync->cbs_table = cbs_table; + + return sync; +} + +void +msn_sync_destroy(MsnSync *sync) +{ + g_free(sync); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/sync.h Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,52 @@ +/** + * @file sync.h MSN list synchronization functions + * + * 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 _MSN_SYNC_H_ +#define _MSN_SYNC_H_ + +typedef struct _MsnSync MsnSync; + +#include "session.h" +#include "table.h" +#include "user.h" + +struct _MsnSync +{ + MsnSession *session; + MsnTable *cbs_table; + MsnTable *old_cbs_table; + + int num_users; + int total_users; + int num_groups; + int total_groups; + MsnUser *last_user; +}; + +void msn_sync_init(void); +void msn_sync_end(void); + +MsnSync * msn_sync_new(MsnSession *session); +void msn_sync_destroy(MsnSync *sync); + +#endif /* _MSN_SYNC_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/table.c Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,132 @@ +/** + * @file table.c MSN helper structure + * + * 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 "msn.h" +#include "table.h" + +static void +null_cmd_cb(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ +} + +static void +null_error_cb(MsnCmdProc *cmdproc, MsnTransaction *trans, int error) +{ +} + +MsnTable * +msn_table_new() +{ + MsnTable *table; + + table = g_new0(MsnTable, 1); + + table->cmds = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)g_hash_table_destroy); + table->msgs = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); + table->errors = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); + + table->async = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); + table->fallback = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); + + return table; +} + +void +msn_table_destroy(MsnTable *table) +{ + g_return_if_fail(table != NULL); + + g_hash_table_destroy(table->cmds); + g_hash_table_destroy(table->msgs); + g_hash_table_destroy(table->errors); + + g_hash_table_destroy(table->async); + g_hash_table_destroy(table->fallback); + + g_free(table); +} + +void +msn_table_add_cmd(MsnTable *table, + char *command, char *answer, MsnTransCb cb) +{ + GHashTable *cbs; + + g_return_if_fail(table != NULL); + g_return_if_fail(answer != NULL); + + cbs = NULL; + + if (command == NULL) + { + cbs = table->async; + } + else if (strcmp(command, "fallback") == 0) + { + cbs = table->fallback; + } + else + { + cbs = g_hash_table_lookup(table->cmds, command); + + if (cbs == NULL) + { + cbs = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); + g_hash_table_insert(table->cmds, command, cbs); + } + } + + if (cb == NULL) + cb = null_cmd_cb; + + g_hash_table_insert(cbs, answer, cb); +} + +void +msn_table_add_error(MsnTable *table, + char *answer, MsnErrorCb cb) +{ + g_return_if_fail(table != NULL); + g_return_if_fail(answer != NULL); + + if (cb == NULL) + cb = null_error_cb; + + g_hash_table_insert(table->errors, answer, cb); +} + +void +msn_table_add_msg_type(MsnTable *table, + char *type, MsnMsgTypeCb cb) +{ + g_return_if_fail(table != NULL); + g_return_if_fail(type != NULL); + g_return_if_fail(cb != NULL); + +#if 0 + if (cb == NULL) + cb = null_msg_cb; +#endif + + g_hash_table_insert(table->msgs, type, cb); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/table.h Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,53 @@ +/** + * @file table.h MSN helper structure + * + * 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 _MSN_TABLE_H_ +#define _MSN_TABLE_H_ + +typedef struct _MsnTable MsnTable; + +#include "cmdproc.h" +#include "transaction.h" +#include "msg.h" + +typedef void (*MsnMsgTypeCb)(MsnCmdProc *cmdproc, MsnMessage *msg); + +struct _MsnTable +{ + GHashTable *cmds; + GHashTable *msgs; + GHashTable *errors; + + GHashTable *async; + GHashTable *fallback; +}; + +MsnTable *msn_table_new(void); +void msn_table_destroy(MsnTable *table); + +void msn_table_add_cmd(MsnTable *table, char *command, char *answer, + MsnTransCb cb); +void msn_table_add_error(MsnTable *table, char *answer, MsnErrorCb cb); +void msn_table_add_msg_type(MsnTable *table, char *type, MsnMsgTypeCb cb); + +#endif /* _MSN_TABLE_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/transaction.c Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,221 @@ +/** + * @file transaction.c MSN transaction functions + * + * 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 "msn.h" +#include "transaction.h" + +MsnTransaction * +msn_transaction_new(MsnCmdProc *cmdproc, const char *command, + const char *format, ...) +{ + MsnTransaction *trans; + va_list arg; + + g_return_val_if_fail(command != NULL, NULL); + + trans = g_new0(MsnTransaction, 1); + + trans->cmdproc = cmdproc; + trans->command = g_strdup(command); + + if (format != NULL) + { + va_start(arg, format); + trans->params = g_strdup_vprintf(format, arg); + va_end(arg); + } + + /* trans->queue = g_queue_new(); */ + + return trans; +} + +void +msn_transaction_destroy(MsnTransaction *trans) +{ + g_return_if_fail(trans != NULL); + + g_free(trans->command); + g_free(trans->params); + g_free(trans->payload); + +#if 0 + if (trans->pendent_cmd != NULL) + msn_message_unref(trans->pendent_msg); +#endif + +#if 0 + MsnTransaction *elem; + if (trans->queue != NULL) + { + while ((elem = g_queue_pop_head(trans->queue)) != NULL) + msn_transaction_destroy(elem); + + g_queue_free(trans->queue); + } +#endif + + if (trans->callbacks != NULL && trans->has_custom_callbacks) + g_hash_table_destroy(trans->callbacks); + + if (trans->timer) + purple_timeout_remove(trans->timer); + + g_free(trans); +} + +char * +msn_transaction_to_string(MsnTransaction *trans) +{ + char *str; + + g_return_val_if_fail(trans != NULL, FALSE); + + if (trans->params != NULL) + str = g_strdup_printf("%s %u %s\r\n", trans->command, trans->trId, trans->params); + else + str = g_strdup_printf("%s %u\r\n", trans->command, trans->trId); + + return str; +} + +void +msn_transaction_queue_cmd(MsnTransaction *trans, MsnCommand *cmd) +{ + purple_debug_info("msn", "queueing command.\n"); + trans->pendent_cmd = cmd; + msn_command_ref(cmd); +} + +void +msn_transaction_unqueue_cmd(MsnTransaction *trans, MsnCmdProc *cmdproc) +{ + MsnCommand *cmd; + + if (!cmdproc->servconn->connected) + return; + + purple_debug_info("msn", "unqueueing command.\n"); + cmd = trans->pendent_cmd; + + g_return_if_fail(cmd != NULL); + + msn_cmdproc_process_cmd(cmdproc, cmd); + msn_command_unref(cmd); + + trans->pendent_cmd = NULL; +} + +#if 0 +void +msn_transaction_queue(MsnTransaction *trans, MsnTransaction *elem) +{ + if (trans->queue == NULL) + trans->queue = g_queue_new(); + + g_queue_push_tail(trans->queue, elem); +} + +void +msn_transaction_unqueue(MsnTransaction *trans, MsnCmdProc *cmdproc) +{ + MsnTransaction *elem; + + while ((elem = g_queue_pop_head(trans->queue)) != NULL) + msn_cmdproc_send_trans(cmdproc, elem); +} +#endif + +void +msn_transaction_set_payload(MsnTransaction *trans, + const char *payload, int payload_len) +{ + g_return_if_fail(trans != NULL); + g_return_if_fail(payload != NULL); + + trans->payload = g_strdup(payload); + trans->payload_len = payload_len ? payload_len : strlen(trans->payload); +} + +void +msn_transaction_set_data(MsnTransaction *trans, void *data) +{ + g_return_if_fail(trans != NULL); + + trans->data = data; +} + +void +msn_transaction_add_cb(MsnTransaction *trans, char *answer, + MsnTransCb cb) +{ + g_return_if_fail(trans != NULL); + g_return_if_fail(answer != NULL); + + if (trans->callbacks == NULL) + { + trans->has_custom_callbacks = TRUE; + trans->callbacks = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, + NULL); + } + else if (trans->has_custom_callbacks != TRUE) + g_return_if_reached (); + + g_hash_table_insert(trans->callbacks, answer, cb); +} + +static gboolean +transaction_timeout(gpointer data) +{ + MsnTransaction *trans; + + trans = data; + g_return_val_if_fail(trans != NULL, FALSE); + +#if 0 + purple_debug_info("msn", "timed out: %s %d %s\n", trans->command, trans->trId, trans->params); +#endif + + if (trans->timeout_cb != NULL) + trans->timeout_cb(trans->cmdproc, trans); + + return FALSE; +} + +void +msn_transaction_set_timeout_cb(MsnTransaction *trans, MsnTimeoutCb cb) +{ + if (trans->timer) + { + purple_debug_error("msn", "This shouldn't be happening\n"); + purple_timeout_remove(trans->timer); + } + trans->timeout_cb = cb; + trans->timer = purple_timeout_add(60000, transaction_timeout, trans); +} + +void +msn_transaction_set_error_cb(MsnTransaction *trans, MsnErrorCb cb) +{ + trans->error_cb = cb; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/transaction.h Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,80 @@ +/** + * @file transaction.h MSN transaction functions + * + * 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 _MSN_TRANSACTION_H +#define _MSN_TRANSACTION_H + +typedef struct _MsnTransaction MsnTransaction; + +#include "command.h" +#include "cmdproc.h" + +typedef void (*MsnTransCb)(MsnCmdProc *cmdproc, MsnCommand *cmd); +typedef void (*MsnTimeoutCb)(MsnCmdProc *cmdproc, MsnTransaction *trans); +typedef void (*MsnErrorCb)(MsnCmdProc *cmdproc, MsnTransaction *trans, + int error); + +/** + * A transaction. A sending command that will initiate the transaction. + */ +struct _MsnTransaction +{ + MsnCmdProc *cmdproc; + unsigned int trId; + + char *command; + char *params; + + int timer; + + void *data; /**< The data to be used on the different callbacks. */ + GHashTable *callbacks; + gboolean has_custom_callbacks; + MsnErrorCb error_cb; + MsnTimeoutCb timeout_cb; + + char *payload; + size_t payload_len; + + GQueue *queue; + MsnCommand *pendent_cmd; /**< The command that is waiting for the result of + this transaction. */ +}; + +MsnTransaction *msn_transaction_new(MsnCmdProc *cmdproc, + const char *command, + const char *format, ...); +void msn_transaction_destroy(MsnTransaction *trans); + +char *msn_transaction_to_string(MsnTransaction *trans); +void msn_transaction_queue_cmd(MsnTransaction *trans, MsnCommand *cmd); +void msn_transaction_unqueue_cmd(MsnTransaction *trans, MsnCmdProc *cmdproc); +void msn_transaction_set_payload(MsnTransaction *trans, + const char *payload, int payload_len); +void msn_transaction_set_data(MsnTransaction *trans, void *data); +void msn_transaction_add_cb(MsnTransaction *trans, char *answer, + MsnTransCb cb); +void msn_transaction_set_error_cb(MsnTransaction *trans, MsnErrorCb cb); +void msn_transaction_set_timeout_cb(MsnTransaction *trans, MsnTimeoutCb cb); + +#endif /* _MSN_TRANSACTION_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/user.c Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,392 @@ +/** + * @file user.c User functions + * + * 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 "msn.h" +#include "user.h" +#include "slp.h" + +MsnUser * +msn_user_new(MsnUserList *userlist, const char *passport, + const char *store_name) +{ + MsnUser *user; + + user = g_new0(MsnUser, 1); + + user->userlist = userlist; + + msn_user_set_passport(user, passport); + msn_user_set_store_name(user, store_name); + + /* + * XXX This seems to reset the friendly name from what it should be + * to the passport when moving users. So, screw it :) + */ +#if 0 + if (name != NULL) + msn_user_set_name(user, name); +#endif + + return user; +} + +void +msn_user_destroy(MsnUser *user) +{ + g_return_if_fail(user != NULL); + + if (user->clientcaps != NULL) + g_hash_table_destroy(user->clientcaps); + + if (user->group_ids != NULL) + g_list_free(user->group_ids); + + if (user->msnobj != NULL) + msn_object_destroy(user->msnobj); + + g_free(user->passport); + g_free(user->friendly_name); + g_free(user->store_name); + g_free(user->phone.home); + g_free(user->phone.work); + g_free(user->phone.mobile); + + g_free(user); +} + +void +msn_user_update(MsnUser *user) +{ + PurpleAccount *account; + + account = user->userlist->session->account; + + if (user->status != NULL) { + if (!strcmp(user->status, "offline") && user->mobile) { + purple_prpl_got_user_status(account, user->passport, "offline", NULL); + purple_prpl_got_user_status(account, user->passport, "mobile", NULL); + } else { + purple_prpl_got_user_status(account, user->passport, user->status, NULL); + purple_prpl_got_user_status_deactive(account, user->passport, "mobile"); + } + } + + if (user->idle) + purple_prpl_got_user_idle(account, user->passport, TRUE, -1); + else + purple_prpl_got_user_idle(account, user->passport, FALSE, 0); +} + +void +msn_user_set_state(MsnUser *user, const char *state) +{ + const char *status; + + if (!g_ascii_strcasecmp(state, "BSY")) + status = "busy"; + else if (!g_ascii_strcasecmp(state, "BRB")) + status = "brb"; + else if (!g_ascii_strcasecmp(state, "AWY")) + status = "away"; + else if (!g_ascii_strcasecmp(state, "PHN")) + status = "phone"; + else if (!g_ascii_strcasecmp(state, "LUN")) + status = "lunch"; + else + status = "available"; + + if (!g_ascii_strcasecmp(state, "IDL")) + user->idle = TRUE; + else + user->idle = FALSE; + + user->status = status; +} + +void +msn_user_set_passport(MsnUser *user, const char *passport) +{ + g_return_if_fail(user != NULL); + + g_free(user->passport); + user->passport = g_strdup(passport); +} + +void +msn_user_set_friendly_name(MsnUser *user, const char *name) +{ + g_return_if_fail(user != NULL); + + g_free(user->friendly_name); + user->friendly_name = g_strdup(name); +} + +void +msn_user_set_store_name(MsnUser *user, const char *name) +{ + g_return_if_fail(user != NULL); + + g_free(user->store_name); + user->store_name = g_strdup(name); +} + +void +msn_user_set_buddy_icon(MsnUser *user, PurpleStoredImage *img) +{ + MsnObject *msnobj = msn_user_get_object(user); + + g_return_if_fail(user != NULL); + + if (img == NULL) + msn_user_set_object(user, NULL); + else + { + PurpleCipherContext *ctx; + char *buf; + gconstpointer data = purple_imgstore_get_data(img); + size_t size = purple_imgstore_get_size(img); + char *base64; + unsigned char digest[20]; + + if (msnobj == NULL) + { + msnobj = msn_object_new(); + msn_object_set_local(msnobj); + msn_object_set_type(msnobj, MSN_OBJECT_USERTILE); + msn_object_set_location(msnobj, "TFR2C2.tmp"); + msn_object_set_creator(msnobj, msn_user_get_passport(user)); + + msn_user_set_object(user, msnobj); + } + + msn_object_set_image(msnobj, img); + + /* Compute the SHA1D field. */ + memset(digest, 0, sizeof(digest)); + + ctx = purple_cipher_context_new_by_name("sha1", NULL); + purple_cipher_context_append(ctx, data, size); + purple_cipher_context_digest(ctx, sizeof(digest), digest, NULL); + + base64 = purple_base64_encode(digest, sizeof(digest)); + msn_object_set_sha1d(msnobj, base64); + g_free(base64); + + msn_object_set_size(msnobj, size); + + /* Compute the SHA1C field. */ + buf = g_strdup_printf( + "Creator%sSize%dType%dLocation%sFriendly%sSHA1D%s", + msn_object_get_creator(msnobj), + msn_object_get_size(msnobj), + msn_object_get_type(msnobj), + msn_object_get_location(msnobj), + msn_object_get_friendly(msnobj), + msn_object_get_sha1d(msnobj)); + + memset(digest, 0, sizeof(digest)); + + purple_cipher_context_reset(ctx, NULL); + purple_cipher_context_append(ctx, (const guchar *)buf, strlen(buf)); + purple_cipher_context_digest(ctx, sizeof(digest), digest, NULL); + purple_cipher_context_destroy(ctx); + g_free(buf); + + base64 = purple_base64_encode(digest, sizeof(digest)); + msn_object_set_sha1c(msnobj, base64); + g_free(base64); + } +} + +void +msn_user_add_group_id(MsnUser *user, int id) +{ + MsnUserList *userlist; + PurpleAccount *account; + PurpleBuddy *b; + PurpleGroup *g; + const char *passport; + const char *group_name; + + g_return_if_fail(user != NULL); + g_return_if_fail(id >= 0); + + user->group_ids = g_list_append(user->group_ids, GINT_TO_POINTER(id)); + + userlist = user->userlist; + account = userlist->session->account; + passport = msn_user_get_passport(user); + + group_name = msn_userlist_find_group_name(userlist, id); + + g = purple_find_group(group_name); + + if ((id == 0) && (g == NULL)) + { + g = purple_group_new(group_name); + purple_blist_add_group(g, NULL); + } + + b = purple_find_buddy_in_group(account, passport, g); + + if (b == NULL) + { + b = purple_buddy_new(account, passport, NULL); + + purple_blist_add_buddy(b, NULL, g, NULL); + } + + b->proto_data = user; +} + +void +msn_user_remove_group_id(MsnUser *user, int id) +{ + g_return_if_fail(user != NULL); + g_return_if_fail(id >= 0); + + user->group_ids = g_list_remove(user->group_ids, GINT_TO_POINTER(id)); +} + +void +msn_user_set_home_phone(MsnUser *user, const char *number) +{ + g_return_if_fail(user != NULL); + + if (user->phone.home != NULL) + g_free(user->phone.home); + + user->phone.home = (number == NULL ? NULL : g_strdup(number)); +} + +void +msn_user_set_work_phone(MsnUser *user, const char *number) +{ + g_return_if_fail(user != NULL); + + if (user->phone.work != NULL) + g_free(user->phone.work); + + user->phone.work = (number == NULL ? NULL : g_strdup(number)); +} + +void +msn_user_set_mobile_phone(MsnUser *user, const char *number) +{ + g_return_if_fail(user != NULL); + + if (user->phone.mobile != NULL) + g_free(user->phone.mobile); + + user->phone.mobile = (number == NULL ? NULL : g_strdup(number)); +} + +void +msn_user_set_object(MsnUser *user, MsnObject *obj) +{ + g_return_if_fail(user != NULL); + + if (user->msnobj != NULL) + msn_object_destroy(user->msnobj); + + user->msnobj = obj; + + if (user->list_op & MSN_LIST_FL_OP) + msn_queue_buddy_icon_request(user); +} + +void +msn_user_set_client_caps(MsnUser *user, GHashTable *info) +{ + g_return_if_fail(user != NULL); + g_return_if_fail(info != NULL); + + if (user->clientcaps != NULL) + g_hash_table_destroy(user->clientcaps); + + user->clientcaps = info; +} + +const char * +msn_user_get_passport(const MsnUser *user) +{ + g_return_val_if_fail(user != NULL, NULL); + + return user->passport; +} + +const char * +msn_user_get_friendly_name(const MsnUser *user) +{ + g_return_val_if_fail(user != NULL, NULL); + + return user->friendly_name; +} + +const char * +msn_user_get_store_name(const MsnUser *user) +{ + g_return_val_if_fail(user != NULL, NULL); + + return user->store_name; +} + +const char * +msn_user_get_home_phone(const MsnUser *user) +{ + g_return_val_if_fail(user != NULL, NULL); + + return user->phone.home; +} + +const char * +msn_user_get_work_phone(const MsnUser *user) +{ + g_return_val_if_fail(user != NULL, NULL); + + return user->phone.work; +} + +const char * +msn_user_get_mobile_phone(const MsnUser *user) +{ + g_return_val_if_fail(user != NULL, NULL); + + return user->phone.mobile; +} + +MsnObject * +msn_user_get_object(const MsnUser *user) +{ + g_return_val_if_fail(user != NULL, NULL); + + return user->msnobj; +} + +GHashTable * +msn_user_get_client_caps(const MsnUser *user) +{ + g_return_val_if_fail(user != NULL, NULL); + + return user->clientcaps; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/user.h Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,284 @@ +/** + * @file user.h User functions + * + * 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 _MSN_USER_H_ +#define _MSN_USER_H_ + +typedef struct _MsnUser MsnUser; + +#include "session.h" +#include "object.h" + +#include "userlist.h" + +/** + * A user. + */ +struct _MsnUser +{ +#if 0 + MsnSession *session; /**< The MSN session. */ +#endif + MsnUserList *userlist; + + char *passport; /**< The passport account. */ + char *store_name; /**< The name stored in the server. */ + char *friendly_name; /**< The friendly name. */ + + const char *status; /**< The state of the user. */ + gboolean idle; /**< The idle state of the user. */ + + struct + { + char *home; /**< Home phone number. */ + char *work; /**< Work phone number. */ + char *mobile; /**< Mobile phone number. */ + + } phone; + + gboolean authorized; /**< Authorized to add this user. */ + gboolean mobile; /**< Signed up with MSN Mobile. */ + + GList *group_ids; /**< The group IDs. */ + + MsnObject *msnobj; /**< The user's MSN Object. */ + + GHashTable *clientcaps; /**< The client's capabilities. */ + + int list_op; +}; + +/**************************************************************************/ +/** @name User API */ +/**************************************************************************/ +/*@{*/ + +/** + * Creates a new user structure. + * + * @param session The MSN session. + * @param passport The initial passport. + * @param stored_name The initial stored name. + * + * @return A new user structure. + */ +MsnUser *msn_user_new(MsnUserList *userlist, const char *passport, + const char *store_name); + +/** + * Destroys a user structure. + * + * @param user The user to destroy. + */ +void msn_user_destroy(MsnUser *user); + + +/** + * Updates the user. + * + * Communicates with the core to update the ui, etc. + * + * @param user The user to update. + */ +void msn_user_update(MsnUser *user); + +/** + * Sets the new state of user. + * + * @param user The user. + * @param state The state string. + */ +void msn_user_set_state(MsnUser *user, const char *state); + +/** + * Sets the passport account for a user. + * + * @param user The user. + * @param passport The passport account. + */ +void msn_user_set_passport(MsnUser *user, const char *passport); + +/** + * Sets the friendly name for a user. + * + * @param user The user. + * @param name The friendly name. + */ +void msn_user_set_friendly_name(MsnUser *user, const char *name); + +/** + * Sets the store name for a user. + * + * @param user The user. + * @param name The store name. + */ +void msn_user_set_store_name(MsnUser *user, const char *name); + +/** + * Sets the buddy icon for a local user. + * + * @param user The user. + * @param img The buddy icon image + */ +void msn_user_set_buddy_icon(MsnUser *user, PurpleStoredImage *img); + +/** + * Sets the group ID list for a user. + * + * @param user The user. + * @param ids The group ID list. + */ +void msn_user_set_group_ids(MsnUser *user, GList *ids); + +/** + * Adds the group ID for a user. + * + * @param user The user. + * @param id The group ID. + */ +void msn_user_add_group_id(MsnUser *user, int id); + +/** + * Removes the group ID from a user. + * + * @param user The user. + * @param id The group ID. + */ +void msn_user_remove_group_id(MsnUser *user, int id); + +/** + * Sets the home phone number for a user. + * + * @param user The user. + * @param number The home phone number. + */ +void msn_user_set_home_phone(MsnUser *user, const char *number); + +/** + * Sets the work phone number for a user. + * + * @param user The user. + * @param number The work phone number. + */ +void msn_user_set_work_phone(MsnUser *user, const char *number); + +/** + * Sets the mobile phone number for a user. + * + * @param user The user. + * @param number The mobile phone number. + */ +void msn_user_set_mobile_phone(MsnUser *user, const char *number); + +/** + * Sets the MSNObject for a user. + * + * @param user The user. + * @param obj The MSNObject. + */ +void msn_user_set_object(MsnUser *user, MsnObject *obj); + +/** + * Sets the client information for a user. + * + * @param user The user. + * @param info The client information. + */ +void msn_user_set_client_caps(MsnUser *user, GHashTable *info); + + +/** + * Returns the passport account for a user. + * + * @param user The user. + * + * @return The passport account. + */ +const char *msn_user_get_passport(const MsnUser *user); + +/** + * Returns the friendly name for a user. + * + * @param user The user. + * + * @return The friendly name. + */ +const char *msn_user_get_friendly_name(const MsnUser *user); + +/** + * Returns the store name for a user. + * + * @param user The user. + * + * @return The store name. + */ +const char *msn_user_get_store_name(const MsnUser *user); + +/** + * Returns the home phone number for a user. + * + * @param user The user. + * + * @return The user's home phone number. + */ +const char *msn_user_get_home_phone(const MsnUser *user); + +/** + * Returns the work phone number for a user. + * + * @param user The user. + * + * @return The user's work phone number. + */ +const char *msn_user_get_work_phone(const MsnUser *user); + +/** + * Returns the mobile phone number for a user. + * + * @param user The user. + * + * @return The user's mobile phone number. + */ +const char *msn_user_get_mobile_phone(const MsnUser *user); + +/** + * Returns the MSNObject for a user. + * + * @param user The user. + * + * @return The MSNObject. + */ +MsnObject *msn_user_get_object(const MsnUser *user); + +/** + * Returns the client information for a user. + * + * @param user The user. + * + * @return The client information. + */ +GHashTable *msn_user_get_client_caps(const MsnUser *user); + +/*@}*/ + +#endif /* _MSN_USER_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/userlist.c Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,709 @@ +/** + * @file userlist.c MSN user list support + * + * 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 "msn.h" +#include "userlist.h" + +const char *lists[] = { "FL", "AL", "BL", "RL" }; + +typedef struct +{ + PurpleConnection *gc; + char *who; + char *friendly; + +} MsnPermitAdd; + +/************************************************************************** + * Callbacks + **************************************************************************/ +static void +msn_accept_add_cb(gpointer data) +{ + MsnPermitAdd *pa = data; + MsnSession *session; + MsnUserList *userlist; + + if (PURPLE_CONNECTION_IS_VALID(pa->gc)) { + session = pa->gc->proto_data; + userlist = session->userlist; + + msn_userlist_add_buddy(userlist, pa->who, MSN_LIST_AL, NULL); + } + + g_free(pa->who); + g_free(pa->friendly); + g_free(pa); +} + +static void +msn_cancel_add_cb(gpointer data) +{ + MsnPermitAdd *pa = data; + MsnSession *session; + MsnUserList *userlist; + + if (PURPLE_CONNECTION_IS_VALID(pa->gc)) { + session = pa->gc->proto_data; + userlist = session->userlist; + + msn_userlist_add_buddy(userlist, pa->who, MSN_LIST_BL, NULL); + } + + g_free(pa->who); + g_free(pa->friendly); + g_free(pa); +} + +static void +got_new_entry(PurpleConnection *gc, const char *passport, const char *friendly) +{ + MsnPermitAdd *pa; + + pa = g_new0(MsnPermitAdd, 1); + pa->who = g_strdup(passport); + pa->friendly = g_strdup(friendly); + pa->gc = gc; + + purple_account_request_authorization(purple_connection_get_account(gc), passport, NULL, friendly, NULL, + purple_find_buddy(purple_connection_get_account(gc), passport) != NULL, + msn_accept_add_cb, msn_cancel_add_cb, pa); +} + +/************************************************************************** + * Utility functions + **************************************************************************/ + +static gboolean +user_is_in_group(MsnUser *user, int group_id) +{ + if (user == NULL) + return FALSE; + + if (group_id < 0) + return FALSE; + + if (g_list_find(user->group_ids, GINT_TO_POINTER(group_id))) + return TRUE; + + return FALSE; +} + +static gboolean +user_is_there(MsnUser *user, int list_id, int group_id) +{ + int list_op; + + if (user == NULL) + return FALSE; + + list_op = 1 << list_id; + + if (!(user->list_op & list_op)) + return FALSE; + + if (list_id == MSN_LIST_FL) + { + if (group_id >= 0) + return user_is_in_group(user, group_id); + } + + return TRUE; +} + +static const char* +get_store_name(MsnUser *user) +{ + const char *store_name; + + g_return_val_if_fail(user != NULL, NULL); + + store_name = msn_user_get_store_name(user); + + if (store_name != NULL) + store_name = purple_url_encode(store_name); + else + store_name = msn_user_get_passport(user); + + /* this might be a bit of a hack, but it should prevent notification server + * disconnections for people who have buddies with insane friendly names + * who added you to their buddy list from being disconnected. Stu. */ + /* Shx: What? Isn't the store_name obtained from the server, and hence it's + * below the BUDDY_ALIAS_MAXLEN ? */ + /* Stu: yeah, that's why it's a bit of a hack, as you pointed out, we're + * probably decoding the incoming store_name wrong, or something. bleh. */ + + if (strlen(store_name) > BUDDY_ALIAS_MAXLEN) + store_name = msn_user_get_passport(user); + + return store_name; +} + +static void +msn_request_add_group(MsnUserList *userlist, const char *who, + const char *old_group_name, const char *new_group_name) +{ + MsnCmdProc *cmdproc; + MsnTransaction *trans; + MsnMoveBuddy *data; + + cmdproc = userlist->session->notification->cmdproc; + data = g_new0(MsnMoveBuddy, 1); + + data->who = g_strdup(who); + + if (old_group_name) + data->old_group_name = g_strdup(old_group_name); + + trans = msn_transaction_new(cmdproc, "ADG", "%s %d", + purple_url_encode(new_group_name), + 0); + + msn_transaction_set_data(trans, data); + + msn_cmdproc_send_trans(cmdproc, trans); +} + +/************************************************************************** + * Server functions + **************************************************************************/ + +MsnListId +msn_get_list_id(const char *list) +{ + if (list[0] == 'F') + return MSN_LIST_FL; + else if (list[0] == 'A') + return MSN_LIST_AL; + else if (list[0] == 'B') + return MSN_LIST_BL; + else if (list[0] == 'R') + return MSN_LIST_RL; + + return -1; +} + +void +msn_got_add_user(MsnSession *session, MsnUser *user, + MsnListId list_id, int group_id) +{ + PurpleAccount *account; + const char *passport; + const char *friendly; + + account = session->account; + + passport = msn_user_get_passport(user); + friendly = msn_user_get_friendly_name(user); + + if (list_id == MSN_LIST_FL) + { + PurpleConnection *gc; + + gc = purple_account_get_connection(account); + + serv_got_alias(gc, passport, friendly); + + if (group_id >= 0) + { + msn_user_add_group_id(user, group_id); + } + else + { + /* session->sync->fl_users_count++; */ + } + } + else if (list_id == MSN_LIST_AL) + { + purple_privacy_permit_add(account, passport, TRUE); + } + else if (list_id == MSN_LIST_BL) + { + purple_privacy_deny_add(account, passport, TRUE); + } + else if (list_id == MSN_LIST_RL) + { + PurpleConnection *gc; + PurpleConversation *convo; + + gc = purple_account_get_connection(account); + + purple_debug_info("msn", + "%s has added you to his or her buddy list.\n", + passport); + + convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, passport, account); + if (convo) { + PurpleBuddy *buddy; + char *msg; + + buddy = purple_find_buddy(account, passport); + msg = g_strdup_printf( + _("%s has added you to his or her buddy list."), + buddy ? purple_buddy_get_contact_alias(buddy) : passport); + purple_conv_im_write(PURPLE_CONV_IM(convo), passport, msg, + PURPLE_MESSAGE_SYSTEM, time(NULL)); + g_free(msg); + } + + if (!(user->list_op & (MSN_LIST_AL_OP | MSN_LIST_BL_OP))) + { + /* + * TODO: The friendly name was NULL for me when I + * looked at this. Maybe we should use the store + * name instead? --KingAnt + */ + got_new_entry(gc, passport, friendly); + } + } + + user->list_op |= (1 << list_id); + /* purple_user_add_list_id (user, list_id); */ +} + +void +msn_got_rem_user(MsnSession *session, MsnUser *user, + MsnListId list_id, int group_id) +{ + PurpleAccount *account; + const char *passport; + + account = session->account; + + passport = msn_user_get_passport(user); + + if (list_id == MSN_LIST_FL) + { + /* TODO: When is the user totally removed? */ + if (group_id >= 0) + { + msn_user_remove_group_id(user, group_id); + return; + } + else + { + /* session->sync->fl_users_count--; */ + } + } + else if (list_id == MSN_LIST_AL) + { + purple_privacy_permit_remove(account, passport, TRUE); + } + else if (list_id == MSN_LIST_BL) + { + purple_privacy_deny_remove(account, passport, TRUE); + } + else if (list_id == MSN_LIST_RL) + { + PurpleConversation *convo; + + purple_debug_info("msn", + "%s has removed you from his or her buddy list.\n", + passport); + + convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, passport, account); + if (convo) { + PurpleBuddy *buddy; + char *msg; + + buddy = purple_find_buddy(account, passport); + msg = g_strdup_printf( + _("%s has removed you from his or her buddy list."), + buddy ? purple_buddy_get_contact_alias(buddy) : passport); + purple_conv_im_write(PURPLE_CONV_IM(convo), passport, msg, + PURPLE_MESSAGE_SYSTEM, time(NULL)); + g_free(msg); + } + } + + user->list_op &= ~(1 << list_id); + /* purple_user_remove_list_id (user, list_id); */ + + if (user->list_op == 0) + { + purple_debug_info("msn", "Buddy '%s' shall be deleted?.\n", + passport); + + } +} + +void +msn_got_lst_user(MsnSession *session, MsnUser *user, + int list_op, GSList *group_ids) +{ + PurpleConnection *gc; + PurpleAccount *account; + const char *passport; + const char *store; + + account = session->account; + gc = purple_account_get_connection(account); + + passport = msn_user_get_passport(user); + store = msn_user_get_store_name(user); + + if (list_op & MSN_LIST_FL_OP) + { + GSList *c; + for (c = group_ids; c != NULL; c = g_slist_next(c)) + { + int group_id; + group_id = GPOINTER_TO_INT(c->data); + msn_user_add_group_id(user, group_id); + } + + /* FIXME: It might be a real alias */ + /* Umm, what? This might fix bug #1385130 */ + serv_got_alias(gc, passport, store); + } + + if (list_op & MSN_LIST_AL_OP) + { + /* These are users who are allowed to see our status. */ + purple_privacy_deny_remove(account, passport, TRUE); + purple_privacy_permit_add(account, passport, TRUE); + } + + if (list_op & MSN_LIST_BL_OP) + { + /* These are users who are not allowed to see our status. */ + purple_privacy_permit_remove(account, passport, TRUE); + purple_privacy_deny_add(account, passport, TRUE); + } + + if (list_op & MSN_LIST_RL_OP) + { + /* These are users who have us on their buddy list. */ + /* + * TODO: What is store name set to when this happens? + * For one of my accounts "something@hotmail.com" + * the store name was "something." Maybe we + * should use the friendly name, instead? --KingAnt + */ + + if (!(list_op & (MSN_LIST_AL_OP | MSN_LIST_BL_OP))) + { + got_new_entry(gc, passport, store); + } + } + + user->list_op = list_op; +} + +/************************************************************************** + * UserList functions + **************************************************************************/ + +MsnUserList* +msn_userlist_new(MsnSession *session) +{ + MsnUserList *userlist; + + userlist = g_new0(MsnUserList, 1); + + userlist->session = session; + userlist->buddy_icon_requests = g_queue_new(); + + /* buddy_icon_window is the number of allowed simultaneous buddy icon requests. + * XXX With smarter rate limiting code, we could allow more at once... 5 was the limit set when + * we weren't retrieiving any more than 5 per MSN session. */ + userlist->buddy_icon_window = 1; + + return userlist; +} + +void +msn_userlist_destroy(MsnUserList *userlist) +{ + GList *l; + + for (l = userlist->users; l != NULL; l = l->next) + { + msn_user_destroy(l->data); + } + + g_list_free(userlist->users); + + for (l = userlist->groups; l != NULL; l = l->next) + { + msn_group_destroy(l->data); + } + + g_list_free(userlist->groups); + + g_queue_free(userlist->buddy_icon_requests); + + if (userlist->buddy_icon_request_timer) + purple_timeout_remove(userlist->buddy_icon_request_timer); + + g_free(userlist); +} + +void +msn_userlist_add_user(MsnUserList *userlist, MsnUser *user) +{ + userlist->users = g_list_prepend(userlist->users, user); +} + +void +msn_userlist_remove_user(MsnUserList *userlist, MsnUser *user) +{ + userlist->users = g_list_remove(userlist->users, user); +} + +MsnUser * +msn_userlist_find_user(MsnUserList *userlist, const char *passport) +{ + GList *l; + + g_return_val_if_fail(passport != NULL, NULL); + + for (l = userlist->users; l != NULL; l = l->next) + { + MsnUser *user = (MsnUser *)l->data; + + g_return_val_if_fail(user->passport != NULL, NULL); + + if (!strcmp(passport, user->passport)) + return user; + } + + return NULL; +} + +void +msn_userlist_add_group(MsnUserList *userlist, MsnGroup *group) +{ + userlist->groups = g_list_append(userlist->groups, group); +} + +void +msn_userlist_remove_group(MsnUserList *userlist, MsnGroup *group) +{ + userlist->groups = g_list_remove(userlist->groups, group); +} + +MsnGroup * +msn_userlist_find_group_with_id(MsnUserList *userlist, int id) +{ + GList *l; + + g_return_val_if_fail(userlist != NULL, NULL); + g_return_val_if_fail(id >= 0, NULL); + + for (l = userlist->groups; l != NULL; l = l->next) + { + MsnGroup *group = l->data; + + if (group->id == id) + return group; + } + + return NULL; +} + +MsnGroup * +msn_userlist_find_group_with_name(MsnUserList *userlist, const char *name) +{ + GList *l; + + g_return_val_if_fail(userlist != NULL, NULL); + g_return_val_if_fail(name != NULL, NULL); + + for (l = userlist->groups; l != NULL; l = l->next) + { + MsnGroup *group = l->data; + + if ((group->name != NULL) && !g_ascii_strcasecmp(name, group->name)) + return group; + } + + return NULL; +} + +int +msn_userlist_find_group_id(MsnUserList *userlist, const char *group_name) +{ + MsnGroup *group; + + group = msn_userlist_find_group_with_name(userlist, group_name); + + if (group != NULL) + return msn_group_get_id(group); + else + return -1; +} + +const char * +msn_userlist_find_group_name(MsnUserList *userlist, int group_id) +{ + MsnGroup *group; + + group = msn_userlist_find_group_with_id(userlist, group_id); + + if (group != NULL) + return msn_group_get_name(group); + else + return NULL; +} + +void +msn_userlist_rename_group_id(MsnUserList *userlist, int group_id, + const char *new_name) +{ + MsnGroup *group; + + group = msn_userlist_find_group_with_id(userlist, group_id); + + if (group != NULL) + msn_group_set_name(group, new_name); +} + +void +msn_userlist_remove_group_id(MsnUserList *userlist, int group_id) +{ + MsnGroup *group; + + group = msn_userlist_find_group_with_id(userlist, group_id); + + if (group != NULL) + { + msn_userlist_remove_group(userlist, group); + msn_group_destroy(group); + } +} + +void +msn_userlist_rem_buddy(MsnUserList *userlist, + const char *who, int list_id, const char *group_name) +{ + MsnUser *user; + int group_id; + const char *list; + + user = msn_userlist_find_user(userlist, who); + group_id = -1; + + if (group_name != NULL) + { + group_id = msn_userlist_find_group_id(userlist, group_name); + + if (group_id < 0) + { + /* Whoa, there is no such group. */ + purple_debug_error("msn", "Group doesn't exist: %s\n", group_name); + return; + } + } + + /* First we're going to check if not there. */ + if (!(user_is_there(user, list_id, group_id))) + { + list = lists[list_id]; + purple_debug_error("msn", "User '%s' is not there: %s\n", + who, list); + return; + } + + /* Then request the rem to the server. */ + list = lists[list_id]; + + msn_notification_rem_buddy(userlist->session->notification, list, who, group_id); +} + +void +msn_userlist_add_buddy(MsnUserList *userlist, + const char *who, int list_id, + const char *group_name) +{ + MsnUser *user; + int group_id; + const char *list; + const char *store_name; + + group_id = -1; + + if (!purple_email_is_valid(who)) + { + /* only notify the user about problems adding to the friends list + * maybe we should do something else for other lists, but it probably + * won't cause too many problems if we just ignore it */ + if (list_id == MSN_LIST_FL) + { + char *str = g_strdup_printf(_("Unable to add \"%s\"."), who); + purple_notify_error(NULL, NULL, str, + _("The screen name specified is invalid.")); + g_free(str); + } + + return; + } + + if (group_name != NULL) + { + group_id = msn_userlist_find_group_id(userlist, group_name); + + if (group_id < 0) + { + /* Whoa, we must add that group first. */ + msn_request_add_group(userlist, who, NULL, group_name); + return; + } + } + + user = msn_userlist_find_user(userlist, who); + + /* First we're going to check if it's already there. */ + if (user_is_there(user, list_id, group_id)) + { + list = lists[list_id]; + purple_debug_error("msn", "User '%s' is already there: %s\n", who, list); + return; + } + + store_name = (user != NULL) ? get_store_name(user) : who; + + /* Then request the add to the server. */ + list = lists[list_id]; + + msn_notification_add_buddy(userlist->session->notification, list, who, + store_name, group_id); +} + +void +msn_userlist_move_buddy(MsnUserList *userlist, const char *who, + const char *old_group_name, const char *new_group_name) +{ + int new_group_id; + + new_group_id = msn_userlist_find_group_id(userlist, new_group_name); + + if (new_group_id < 0) + { + msn_request_add_group(userlist, who, old_group_name, new_group_name); + return; + } + + msn_userlist_add_buddy(userlist, who, MSN_LIST_FL, new_group_name); + msn_userlist_rem_buddy(userlist, who, MSN_LIST_FL, old_group_name); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msnp9/userlist.h Sat Nov 17 02:20:01 2007 +0000 @@ -0,0 +1,103 @@ +/** + * @file userlist.h MSN user list support + * + * 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 _MSN_USERLIST_H_ +#define _MSN_USERLIST_H_ + +typedef struct _MsnUserList MsnUserList; + +#include "cmdproc.h" +#include "user.h" +#include "group.h" + +typedef enum +{ + MSN_LIST_FL, + MSN_LIST_AL, + MSN_LIST_BL, + MSN_LIST_RL + +} MsnListId; + +typedef struct +{ + char *who; + char *old_group_name; + +} MsnMoveBuddy; + +struct _MsnUserList +{ + MsnSession *session; + + /* MsnUsers *users; */ + /* MsnGroups *groups; */ + + GList *users; + GList *groups; + + GQueue *buddy_icon_requests; + int buddy_icon_window; + guint buddy_icon_request_timer; + + int fl_users_count; + +}; + +MsnListId msn_get_list_id(const char *list); + +void msn_got_add_user(MsnSession *session, MsnUser *user, + MsnListId list_id, int group_id); +void msn_got_rem_user(MsnSession *session, MsnUser *user, + MsnListId list_id, int group_id); +void msn_got_lst_user(MsnSession *session, MsnUser *user, + int list_op, GSList *group_ids); + +MsnUserList *msn_userlist_new(MsnSession *session); +void msn_userlist_destroy(MsnUserList *userlist); +void msn_userlist_add_user(MsnUserList *userlist, MsnUser *user); +void msn_userlist_remove_user(MsnUserList *userlist, MsnUser *user); +MsnUser *msn_userlist_find_user(MsnUserList *userlist, + const char *passport); +void msn_userlist_add_group(MsnUserList *userlist, MsnGroup *group); +void msn_userlist_remove_group(MsnUserList *userlist, MsnGroup *group); +MsnGroup *msn_userlist_find_group_with_id(MsnUserList *userlist, int id); +MsnGroup *msn_userlist_find_group_with_name(MsnUserList *userlist, + const char *name); +int msn_userlist_find_group_id(MsnUserList *userlist, + const char *group_name); +const char *msn_userlist_find_group_name(MsnUserList *userlist, + int group_id); +void msn_userlist_rename_group_id(MsnUserList *userlist, int group_id, + const char *new_name); +void msn_userlist_remove_group_id(MsnUserList *userlist, int group_id); + +void msn_userlist_rem_buddy(MsnUserList *userlist, const char *who, + int list_id, const char *group_name); +void msn_userlist_add_buddy(MsnUserList *userlist, const char *who, + int list_id, const char *group_name); +void msn_userlist_move_buddy(MsnUserList *userlist, const char *who, + const char *old_group_name, + const char *new_group_name); + +#endif /* _MSN_USERLIST_H_ */
--- a/libpurple/protocols/myspace/Makefile.am Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/myspace/Makefile.am Sat Nov 17 02:20:01 2007 +0000 @@ -38,5 +38,6 @@ AM_CPPFLAGS = \ -I$(top_srcdir)/libpurple \ + -I$(top_builddir)/libpurple \ $(GLIB_CFLAGS) \ $(DEBUG_CFLAGS)
--- a/libpurple/protocols/myspace/markup.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/myspace/markup.c Sat Nov 17 02:20:01 2007 +0000 @@ -400,6 +400,8 @@ msim_markup_tag_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) { + g_return_if_fail(root != NULL); + if (g_str_equal(root->name, "f")) { msim_markup_f_to_html(session, root, begin, end); } else if (g_str_equal(root->name, "a")) { @@ -415,7 +417,7 @@ } else { purple_debug_info("msim", "msim_markup_tag_to_html: " "unknown tag name=%s, ignoring", - (root && root->name) ? root->name : "(NULL)"); + root->name ? root->name : "(NULL)"); *begin = g_strdup(""); *end = g_strdup(""); } @@ -578,7 +580,6 @@ default: purple_debug_info("msim", "msim_convert_xmlnode: strange node\n"); - inner = g_strdup(""); } if (inner) {
--- a/libpurple/protocols/myspace/message.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/myspace/message.c Sat Nov 17 02:20:01 2007 +0000 @@ -314,7 +314,7 @@ MsimMessageElement *elem; MsimMessage **new; gpointer new_data; - + GString *gs; MsimMessage *dict; @@ -349,7 +349,7 @@ default: purple_debug_info("msim", "msim_msg_clone_element: unknown type %d\n", elem->type); - g_return_if_fail(NULL); + g_return_if_reached(); } /* Append cloned data. Note that the 'name' field is a static string, so it @@ -905,7 +905,7 @@ default: g_free(data_string); - g_return_if_fail(FALSE); + g_return_if_reached(); break; }
--- a/libpurple/protocols/myspace/myspace.c Sat Nov 17 02:19:52 2007 +0000 +++ b/libpurple/protocols/myspace/myspace.c Sat Nov 17 02:20:01 2007 +0000 @@ -291,8 +291,8 @@ /* Notify an error message also, because this is important! */ purple_notify_error(acct, _("MySpaceIM Error"), str, NULL); - gc->wants_to_die = TRUE; - purple_connection_error(gc, str); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, str); g_free(str); return; } @@ -315,7 +315,9 @@ 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")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Couldn't create socket")); return; }