pidgin/ljfisher-ssl-client-auth

propagate from branch 'im.pidgin.pidgin' (head e998e98e9f638d88fc7a185e7e171a711307bfc3)
to branch 'im.pidgin.pidgin.next.major' (head b1dbae4ba9a8ed8aedf4b6e449f4f3d6113497aa)
  • +14 -11
    AUTHORS
  • +2 -1
    COPYRIGHT
  • +57 -1
    ChangeLog
  • +26 -0
    ChangeLog.API
  • +0 -1
    configure.ac
  • +20 -3
    finch/finch.c
  • +0 -1
    libpurple/Makefile.am
  • +43 -20
    libpurple/conversation.c
  • +9 -1
    libpurple/conversation.h
  • +21 -1
    libpurple/dnsquery.c
  • +116 -59
    libpurple/dnssrv.c
  • +4 -0
    libpurple/dnssrv.h
  • +26 -22
    libpurple/plugins/log_reader.c
  • +6 -0
    libpurple/prefs.c
  • +92 -93
    libpurple/protocols/gg/gg.c
  • +1 -2
    libpurple/protocols/gg/lib/libgadu.c
  • +1 -0
    libpurple/protocols/gg/lib/libgadu.h
  • +2 -0
    libpurple/protocols/gg/lib/resolver.c
  • +9 -7
    libpurple/protocols/gg/search.c
  • +2 -3
    libpurple/protocols/gg/search.h
  • +17 -7
    libpurple/protocols/irc/irc.c
  • +1 -0
    libpurple/protocols/irc/irc.h
  • +1 -1
    libpurple/protocols/irc/msgs.c
  • +1 -0
    libpurple/protocols/irc/parse.c
  • +5 -5
    libpurple/protocols/jabber/jabber.c
  • +3 -3
    libpurple/protocols/jabber/usermood.c
  • +3 -0
    libpurple/protocols/msn/notification.c
  • +0 -1
    libpurple/protocols/msn/slp.c
  • +0 -2
    libpurple/protocols/msn/slpcall.c
  • +2 -1
    libpurple/protocols/oscar/family_feedbag.c
  • +7 -3
    libpurple/protocols/oscar/oscar.c
  • +5 -0
    libpurple/protocols/sametime/sametime.c
  • +2 -0
    libpurple/protocols/simple/simple.c
  • +13 -6
    libpurple/protocols/yahoo/libymsg.c
  • +1 -1
    libpurple/protocols/yahoo/libymsg.h
  • +1 -0
    libpurple/tests/Makefile.am
  • +1 -0
    libpurple/tests/check_libpurple.c
  • +34 -0
    libpurple/tests/test_xmlnode.c
  • +1 -0
    libpurple/tests/tests.h
  • +3 -5
    libpurple/upnp.c
  • +2 -1
    libpurple/value.h
  • +2 -2
    libpurple/xmlnode.c
  • +1 -1
    pidgin/gtkaccount.c
  • +12 -13
    pidgin/gtkblist.c
  • +118 -87
    pidgin/gtkconv.c
  • +48 -1
    pidgin/gtkdialogs.c
  • +1 -0
    pidgin/gtkdialogs.h
  • +2 -2
    pidgin/gtkft.c
  • +14 -21
    pidgin/gtkimhtml.c
  • +5 -3
    pidgin/gtkmain.c
  • +8 -0
    pidgin/gtkmedia.c
  • +25 -4
    pidgin/gtkprefs.c
  • +23 -33
    pidgin/gtkrequest.c
  • +3 -6
    pidgin/gtksmiley.c
  • +38 -15
    pidgin/gtkstatusbox.c
  • +131 -14
    pidgin/gtkutils.c
  • +106 -0
    pidgin/gtkutils.h
  • +6 -0
    po/ChangeLog
  • +33 -11
    po/de.po
  • --- a/AUTHORS Sun Aug 07 03:21:39 2011 +0000
    +++ b/AUTHORS Sun Aug 07 05:19:54 2011 +0000
    @@ -12,55 +12,58 @@
    Paul 'darkrain42' Aurich - Developer
    John 'rekkanoryo' Bailey - Developer
    Ethan 'Paco-Paco' Blanton - Developer
    -Thomas Butter - Developer
    -Ka-Hing Cheung - Developer
    Sadrul Habib Chowdhury - Developer
    Mark 'KingAnt' Doliner - Developer
    -Sean Egan - Developer
    Casey Harkins - Developer
    Ivan Komarov - Developer
    Gary 'grim' Kramlich - Developer
    Richard 'rlaager' Laager - Developer
    +Marcus 'malu' Lundblad - Developer
    Sulabh 'sulabh_m' Mahajan - Developer
    Richard 'wabz' Nelson - Developer
    -Christopher 'siege' O'Brien - Developer
    -Bartosz Oler - Developer
    Etan 'deryni' Reisner - Developer
    -Tim 'marv' Ringenbach - Developer
    Michael 'Maiku' Ruprecht - Developer, voice and video
    Elliott 'QuLogic' Sales de Andrade - Developer
    Luke 'LSchiere' Schierer - Support
    -Megan 'Cae' Schneider - support/QA
    Evan Schoenberg - Developer
    Kevin 'SimGuy' Stange - Developer & Webmaster
    Will 'resiak' Thompson - Developer
    Stu 'nosnilmot' Tomlinson - Developer
    -Nathan 'faceprint' Walp - Developer
    +Jorge 'Masca' Villaseñor - Developer
    Crazy Patch Writers:
    -------------------
    -Marcus 'malu' Lundblad
    -Dennis 'EvilDennisR' Ristuccia
    +Jakub 'haakon' Adam
    +Krzysztof Klinikowski
    Peter 'Fmoo' Ruibal
    Gabriel 'Nix' Schulhof
    -Jorge 'Masca' Villaseñor
    +Tomasz Wasilczyk
    Retired Developers:
    ------------------
    Herman Bloggs - Win32 Port
    +Thomas Butter - Developer
    +Ka-Hing Cheung - Developer
    Jim Duchek <jim@linuxpimps.com> - maintainer
    +Sean Egan - Developer
    Rob Flynn <gaim@robflynn.com> - maintainer
    Adam Fritzler - libfaim maintainer
    Christian 'ChipX86' Hammond - Developer & Webmaster
    Syd Logan - hacker and designated driver [lazy bum]
    +Christopher 'siege' O'Brien - Developer
    +Bartosz Oler - Developer
    +Tim 'marv' Ringenbach - Developer
    +Megan 'Cae' Schneider - support/QA
    Jim Seymour - XMPP developer
    Mark Spencer <markster@marko.net> - original author
    +Nathan 'faceprint' Walp - Developer
    Eric Warmenhoven <eric@warmenhoven.org> - lead developer
    Retired Crazy Patch Writers:
    ---------------------------
    Felipe 'shx' Contreras
    Decklin Foster
    +Dennis 'EvilDennisR' Ristuccia - Senior Contributor/QA
    Peter 'Bleeter' Lawler
    Robert 'Robot101' McQueen
    Benjamin Miller
    --- a/COPYRIGHT Sun Aug 07 03:21:39 2011 +0000
    +++ b/COPYRIGHT Sun Aug 07 05:19:54 2011 +0000
    @@ -15,7 +15,7 @@
    piece of code, then that code should be traced through our version
    control system to see from where it came and who has modified it.
    -Copyright (C) 1998-2009 by the following:
    +Copyright (C) 1998-2011 by the following:
    Saleem Abdulrasool
    Jakub Adam
    @@ -71,6 +71,7 @@
    Éric Boumaour
    Chris Boyle
    Stanislav Brabec
    +Quentin Brandon
    Derrick J Brashear
    Mauro Sérgio Ferreira Brasil
    Luke Bratch
    --- a/ChangeLog Sun Aug 07 03:21:39 2011 +0000
    +++ b/ChangeLog Sun Aug 07 05:19:54 2011 +0000
    @@ -6,11 +6,67 @@
    * Don't try to format ICQ usernames entered as email addresses.
    Gets rid of an "Unable to format username" error at login. (#13883)
    -version 2.8.1 (MM/DD/YYYY):
    +version 2.10.0 (MM/DD/YYYY):
    + Pidgin:
    + * Make the max size of incoming smileys a pref instead of hardcoding it.
    + (Quentin Brandon) (#5231)
    + * Added a plugin information dialog to show information for plugins
    + that aren't otherwise visible in the plugins dialog.
    + * Fix building with GTK+ earlier than 2.14.0 (GTK+ 2.10 is still the
    + minimum supported) (#14261)
    +
    + libpurple:
    + * Fix a potential crash in the Log Reader plugin when reading QIP logs.
    +
    + Gadu-Gadu:
    + * Fixed searching for buddies in public directory. (Tomasz Wasilczyk)
    + (#5242)
    + * Better status message handling. (Tomasz Wasilczyk) (#14314)
    + * Merged two buddy blocking methods. (Tomasz Wasilczyk) (#5303)
    + * Fix building of the bundled libgadu library with older versions
    + of GnuTLS. (patch plucked from upstream) (#14365)
    +
    + ICQ:
    + * Fix crash selecting Tools->Set Mood when you're online with an
    + ICQ account that is configured as an AIM account. (#14437)
    +
    + IRC:
    + * Fix the handling of formatting following mIRC ^O (#14436)
    +
    + MSN:
    + * Fix seemingly random crashing. (#14307)
    +
    + XMPP:
    + * Do not generate malformed XML ("</>") when setting an empty mood.
    + (#14342)
    + * Fix the /join <room> behavior. (Broken when adding support for
    + <room>@<server>) (#14205)
    +
    + Yahoo!/Yahoo! JAPAN:
    + * Fix coming out of idle while in an unavailable state
    + * Fix logging into Yahoo! JAPAN. (#14259)
    +
    +version 2.9.0 (06/23/2011):
    + Pidgin:
    + * Fix a potential remote denial-of-service bug related to displaying
    + buddy icons.
    + * Significantly improved performance of larger IRC channels (regression
    + introduced in 2.8.0).
    + * Fix Conversation->Add on AIM and MSN.
    + * Entries in the chat user list are sorted properly again. This was
    + inadvertenly broken in 2.8.0.
    +
    + Finch:
    + * Fix logging in to ICQ.
    +
    libpurple:
    * media: Actually use the specified TCP port from the TURN configuration to
    create a TCP relay candidate.
    + AIM and ICQ:
    + * Fix crashes on some non-mainstream OSes when attempting to
    + printf("%s", NULL). (Clemens Huebner) (#14297)
    +
    Plugins:
    * The Evolution Integration plugin compiles again.
    --- a/ChangeLog.API Sun Aug 07 03:21:39 2011 +0000
    +++ b/ChangeLog.API Sun Aug 07 05:19:54 2011 +0000
    @@ -11,6 +11,32 @@
    Removed:
    * purple_core_migrate
    +version 2.10.0:
    + libpurple:
    + Added:
    + * purple_srv_txt_query_destroy (accidentally left out of 2.8.0)
    +
    + Pidgin:
    + Added:
    + * pidgin_dialogs_plugins_info (should not be used by anything but Pidgin)
    +
    +version 2.9.0:
    + libpurple:
    + Added:
    + * Hash table to PurpleConvChat struct, used to make
    + purple_conv_chat_cb_find O(1).
    + * ui_data pointer to PurpleConvChatBuddy struct.
    + * deleting-chat-buddy signal (conversation signals)
    + * pidgin_pixbuf_from_data
    + * pidgin_pixbuf_anim_from_data
    + * pidgin_pixbuf_new_from_file
    + * pidgin_pixbuf_new_from_file_at_size
    + * pidgin_pixbuf_new_from_file_at_scale
    +
    + Deprecated:
    + * purple_conv_chat_set_users
    + * PurpleConvChat in_room list
    +
    version 2.8.0 (06/07/2011):
    libpurple:
    Added:
    --- a/configure.ac Sun Aug 07 03:21:39 2011 +0000
    +++ b/configure.ac Sun Aug 07 05:19:54 2011 +0000
    @@ -571,7 +571,6 @@
    evo_deps="libebook-1.0 libedata-book-1.0"
    PKG_CHECK_MODULES(EVOLUTION_ADDRESSBOOK, $evo_deps, [
    enable_gevolution="yes"
    - ], [
    ])
    fi
    if test "x$enable_gevolution" = "xyes"; then
    --- a/finch/finch.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/finch/finch.c Sun Aug 07 05:19:54 2011 +0000
    @@ -74,11 +74,28 @@
    * account "markdoliner." Please don't use this key for other
    * applications. You can either not specify a client key, in
    * which case the default "libpurple" key will be used, or you
    - * can register for your own client key at
    - * http://developer.aim.com/manageKeys.jsp
    + * can try to register your own at the AIM or ICQ web sites
    + * (although this functionality was removed at some point, it's
    + * possible it has been re-added). AOL's old key management
    + * page is http://developer.aim.com/manageKeys.jsp
    */
    g_hash_table_insert(ui_info, "prpl-aim-clientkey", "ma19sqWV9ymU6UYc");
    - g_hash_table_insert(ui_info, "prpl-icq-clientkey", "ma19sqWV9ymU6UYc");
    +
    + /*
    + * This is the client key for "Pidgin." It is owned by the AIM
    + * account "markdoliner." Please don't use this key for other
    + * applications. You can either not specify a client key, in
    + * which case the default "libpurple" key will be used, or you
    + * can try to register your own at the AIM or ICQ web sites
    + * (although this functionality was removed at some point, it's
    + * possible it has been re-added). AOL's old key management
    + * page is http://developer.aim.com/manageKeys.jsp
    + *
    + * We used to have a Finch-specific devId/clientkey
    + * (ma19sqWV9ymU6UYc), but it stopped working, so we switched
    + * to this one.
    + */
    + g_hash_table_insert(ui_info, "prpl-icq-clientkey", "ma1cSASNCKFtrdv9");
    /*
    * This is the distid for Finch, given to us by AOL. Please
    --- a/libpurple/Makefile.am Sun Aug 07 03:21:39 2011 +0000
    +++ b/libpurple/Makefile.am Sun Aug 07 05:19:54 2011 +0000
    @@ -155,7 +155,6 @@
    theme-manager.h \
    upnp.h \
    util.h \
    - valgrind.h \
    value.h \
    xmlnode.h \
    whiteboard.h
    --- a/libpurple/conversation.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/libpurple/conversation.c Sun Aug 07 05:19:54 2011 +0000
    @@ -70,6 +70,23 @@
    g_free(hc);
    }
    +static guint _purple_conversation_user_hash(gconstpointer data)
    +{
    + const gchar *name = data;
    + gchar *collated;
    + guint hash;
    +
    + collated = g_utf8_collate_key(name, -1);
    + hash = g_str_hash(collated);
    + g_free(collated);
    + return hash;
    +}
    +
    +static gboolean _purple_conversation_user_equal(gconstpointer a, gconstpointer b)
    +{
    + return !g_utf8_collate(a, b);
    +}
    +
    void
    purple_conversations_set_ui_ops(PurpleConversationUiOps *ops)
    {
    @@ -393,6 +410,8 @@
    conv->u.chat = g_new0(PurpleConvChat, 1);
    conv->u.chat->conv = conv;
    + conv->u.chat->users = g_hash_table_new_full(_purple_conversation_user_hash,
    + _purple_conversation_user_equal, g_free, NULL);
    PURPLE_DBUS_REGISTER_POINTER(conv->u.chat, PurpleConvChat);
    chats = g_list_prepend(chats, conv);
    @@ -547,6 +566,8 @@
    conv->u.im = NULL;
    }
    else if (conv->type == PURPLE_CONV_TYPE_CHAT) {
    + g_hash_table_destroy(conv->u.chat->users);
    + conv->u.chat->users = NULL;
    g_list_foreach(conv->u.chat->in_room, (GFunc)purple_conv_chat_cb_destroy, NULL);
    g_list_free(conv->u.chat->in_room);
    @@ -1677,9 +1698,9 @@
    cbuddy = purple_conv_chat_cb_new(user, alias, flag);
    cbuddy->buddy = purple_find_buddy(conv->account, user) != NULL;
    - /* This seems dumb. Why should we set users thousands of times? */
    - purple_conv_chat_set_users(chat,
    - g_list_prepend(chat->in_room, cbuddy));
    +
    + chat->in_room = g_list_prepend(chat->in_room, cbuddy);
    + g_hash_table_replace(chat->users, g_strdup(cbuddy->name), cbuddy);
    cbuddies = g_list_prepend(cbuddies, cbuddy);
    @@ -1771,8 +1792,9 @@
    flags = purple_conv_chat_user_get_flags(chat, old_user);
    cb = purple_conv_chat_cb_new(new_user, new_alias, flags);
    cb->buddy = purple_find_buddy(conv->account, new_user) != NULL;
    - purple_conv_chat_set_users(chat,
    - g_list_prepend(chat->in_room, cb));
    +
    + chat->in_room = g_list_prepend(chat->in_room, cb);
    + g_hash_table_replace(chat->users, g_strdup(cb->name), cb);
    if (ops != NULL && ops->chat_rename_user != NULL)
    ops->chat_rename_user(conv, old_user, new_user, new_alias);
    @@ -1780,8 +1802,8 @@
    cb = purple_conv_chat_cb_find(chat, old_user);
    if (cb) {
    - purple_conv_chat_set_users(chat,
    - g_list_remove(chat->in_room, cb));
    + chat->in_room = g_list_remove(chat->in_room, cb);
    + g_hash_table_remove(chat->users, cb->name);
    purple_conv_chat_cb_destroy(cb);
    }
    @@ -1874,8 +1896,8 @@
    cb = purple_conv_chat_cb_find(chat, user);
    if (cb) {
    - purple_conv_chat_set_users(chat,
    - g_list_remove(chat->in_room, cb));
    + chat->in_room = g_list_remove(chat->in_room, cb);
    + g_hash_table_remove(chat->users, cb->name);
    purple_conv_chat_cb_destroy(cb);
    }
    @@ -1955,8 +1977,10 @@
    purple_conv_chat_cb_destroy(cb);
    }
    + g_hash_table_remove_all(chat->users);
    +
    g_list_free(users);
    - purple_conv_chat_set_users(chat, NULL);
    + chat->in_room = NULL;
    }
    @@ -2146,19 +2170,10 @@
    PurpleConvChatBuddy *
    purple_conv_chat_cb_find(PurpleConvChat *chat, const char *name)
    {
    - GList *l;
    - PurpleConvChatBuddy *cb = NULL;
    -
    g_return_val_if_fail(chat != NULL, NULL);
    g_return_val_if_fail(name != NULL, NULL);
    - for (l = purple_conv_chat_get_users(chat); l; l = l->next) {
    - cb = l->data;
    - if (!g_utf8_collate(cb->name, name))
    - return cb;
    - }
    -
    - return NULL;
    + return g_hash_table_lookup(chat->users, name);
    }
    void
    @@ -2167,6 +2182,9 @@
    if (cb == NULL)
    return;
    + purple_signal_emit(purple_conversations_get_handle(),
    + "deleting-chat-buddy", cb);
    +
    g_free(cb->alias);
    g_free(cb->alias_key);
    g_free(cb->name);
    @@ -2573,6 +2591,11 @@
    purple_value_new(PURPLE_TYPE_STRING),
    purple_value_new(PURPLE_TYPE_STRING));
    + purple_signal_register(handle, "deleting-chat-buddy",
    + purple_marshal_VOID__POINTER, NULL, 1,
    + purple_value_new(PURPLE_TYPE_SUBTYPE,
    + PURPLE_SUBTYPE_CHATBUDDY));
    +
    purple_signal_register(handle, "chat-inviting-user",
    purple_marshal_VOID__POINTER_POINTER_POINTER, NULL, 3,
    purple_value_new(PURPLE_TYPE_SUBTYPE,
    --- a/libpurple/conversation.h Sun Aug 07 03:21:39 2011 +0000
    +++ b/libpurple/conversation.h Sun Aug 07 05:19:54 2011 +0000
    @@ -271,7 +271,9 @@
    {
    PurpleConversation *conv; /**< The parent conversation. */
    - GList *in_room; /**< The users in the room. */
    + GList *in_room; /**< The users in the room.
    + * @deprecated Will be removed in 3.0.0
    + */
    GList *ignored; /**< Ignored users. */
    char *who; /**< The person who set the topic. */
    char *topic; /**< The topic. */
    @@ -279,6 +281,9 @@
    char *nick; /**< Your nick in this chat. */
    gboolean left; /**< We left the chat and kept the window open */
    + GHashTable *users; /**< Hash table of the users in the room.
    + * @since 2.9.0
    + */
    };
    /**
    @@ -304,6 +309,7 @@
    GHashTable *attributes; /**< A hash table of attributes about the user, such as
    * real name, user@host, etc.
    */
    + gpointer ui_data; /** < The UI can put whatever it wants here. */
    };
    /**
    @@ -1065,6 +1071,8 @@
    * @param users The list of users.
    *
    * @return The list passed.
    + *
    + * @deprecated This function will be removed in 3.0.0. You shouldn't be using it anyway.
    */
    GList *purple_conv_chat_set_users(PurpleConvChat *chat, GList *users);
    --- a/libpurple/dnsquery.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/libpurple/dnsquery.c Sun Aug 07 05:19:54 2011 +0000
    @@ -154,8 +154,27 @@
    static gboolean
    resolve_ip(PurpleDnsQueryData *query_data)
    {
    +#if defined(HAVE_GETADDRINFO) && defined(AI_NUMERICHOST)
    + struct addrinfo hints, *res;
    + char servname[20];
    +
    + g_snprintf(servname, sizeof(servname), "%d", query_data->port);
    + memset(&hints, 0, sizeof(hints));
    + hints.ai_family = AF_UNSPEC;
    + hints.ai_flags |= AI_NUMERICHOST;
    +
    + if (0 == getaddrinfo(query_data->hostname, servname, &hints, &res))
    + {
    + GSList *hosts = NULL;
    + hosts = g_slist_append(hosts, GINT_TO_POINTER(res->ai_addrlen));
    + hosts = g_slist_append(hosts, g_memdup(res->ai_addr, res->ai_addrlen));
    + purple_dnsquery_resolved(query_data, hosts);
    +
    + freeaddrinfo(res);
    + return TRUE;
    + }
    +#else /* defined(HAVE_GETADDRINFO) && defined(AI_NUMERICHOST) */
    struct sockaddr_in sin;
    - /* TODO: Use inet_pton for IPv6 support */
    if (inet_aton(query_data->hostname, &sin.sin_addr))
    {
    /*
    @@ -171,6 +190,7 @@
    return TRUE;
    }
    +#endif
    return FALSE;
    }
    --- a/libpurple/dnssrv.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/libpurple/dnssrv.c Sun Aug 07 05:19:54 2011 +0000
    @@ -250,6 +250,52 @@
    return list;
    }
    +static PurpleSrvTxtQueryData *
    +query_data_new(int type, gchar *query, gpointer extradata)
    +{
    + PurpleSrvTxtQueryData *query_data = g_new0(PurpleSrvTxtQueryData, 1);
    + query_data->type = type;
    + query_data->extradata = extradata;
    + query_data->query = query;
    +#ifndef _WIN32
    + query_data->fd_in = -1;
    + query_data->fd_out = -1;
    +#endif
    + return query_data;
    +}
    +
    +void
    +purple_srv_txt_query_destroy(PurpleSrvTxtQueryData *query_data)
    +{
    + PurpleSrvTxtQueryUiOps *ops = purple_srv_txt_query_get_ui_ops();
    +
    + if (ops && ops->destroy)
    + ops->destroy(query_data);
    +
    + if (query_data->handle > 0)
    + purple_input_remove(query_data->handle);
    +#ifdef _WIN32
    + if (query_data->resolver != NULL)
    + {
    + /*
    + * It's not really possible to kill a thread. So instead we
    + * just set the callback to NULL and let the DNS lookup
    + * finish.
    + */
    + query_data->cb.srv = NULL;
    + return;
    + }
    + g_free(query_data->error_message);
    +#else
    + if (query_data->fd_out != -1)
    + close(query_data->fd_out);
    + if (query_data->fd_in != -1)
    + close(query_data->fd_in);
    +#endif
    + g_free(query_data->query);
    + g_free(query_data);
    +}
    +
    #ifdef USE_IDN
    static gboolean
    dns_str_is_ascii(const char *name)
    @@ -523,7 +569,7 @@
    }
    waitpid(query_data->pid, &status, 0);
    - purple_srv_cancel(query_data);
    + purple_srv_txt_query_destroy(query_data);
    }
    #else /* _WIN32 */
    @@ -583,7 +629,7 @@
    query_data->resolver = NULL;
    query_data->handle = 0;
    - purple_srv_cancel(query_data);
    + purple_srv_txt_query_destroy(query_data);
    return FALSE;
    }
    @@ -730,13 +776,10 @@
    purple_debug_info("dnssrv","querying SRV record for %s: %s\n", domain,
    query);
    g_free(hostname);
    -
    - query_data = g_new0(PurpleSrvTxtQueryData, 1);
    - query_data->type = PurpleDnsTypeSrv;
    +
    + query_data = query_data_new(PurpleDnsTypeSrv, query, extradata);
    query_data->cb.srv = cb;
    - query_data->extradata = extradata;
    - query_data->query = query;
    -
    +
    if (purple_srv_txt_query_ui_resolve(query_data))
    {
    return query_data;
    @@ -746,6 +789,7 @@
    if(pipe(in) || pipe(out)) {
    purple_debug_error("dnssrv", "Could not create pipe\n");
    g_free(query);
    + g_free(query_data);
    cb(NULL, 0, extradata);
    return NULL;
    }
    @@ -753,8 +797,9 @@
    pid = fork();
    if (pid == -1) {
    purple_debug_error("dnssrv", "Could not create process!\n");
    + g_free(query);
    + g_free(query_data);
    cb(NULL, 0, extradata);
    - g_free(query);
    return NULL;
    }
    @@ -762,6 +807,7 @@
    if (pid == 0)
    {
    g_free(query);
    + g_free(query_data);
    close(out[0]);
    close(in[1]);
    @@ -784,8 +830,6 @@
    query_data->fd_in = in[1];
    query_data->handle = purple_input_add(out[0], PURPLE_INPUT_READ, resolved, query_data);
    - g_free(query);
    -
    return query_data;
    #else
    if (!initialized) {
    @@ -862,13 +906,10 @@
    purple_debug_info("dnssrv","querying TXT record for %s: %s\n", domain,
    query);
    g_free(hostname);
    -
    - query_data = g_new0(PurpleSrvTxtQueryData, 1);
    - query_data->type = PurpleDnsTypeTxt;
    +
    + query_data = query_data_new(PurpleDnsTypeTxt, query, extradata);
    query_data->cb.txt = cb;
    - query_data->extradata = extradata;
    - query_data->query = query;
    -
    +
    if (purple_srv_txt_query_ui_resolve(query_data)) {
    /* query intentionally not freed
    */
    @@ -879,6 +920,7 @@
    if(pipe(in) || pipe(out)) {
    purple_debug_error("dnssrv", "Could not create pipe\n");
    g_free(query);
    + g_free(query_data);
    cb(NULL, extradata);
    return NULL;
    }
    @@ -886,8 +928,9 @@
    pid = fork();
    if (pid == -1) {
    purple_debug_error("dnssrv", "Could not create process!\n");
    + g_free(query);
    + g_free(query_data);
    cb(NULL, extradata);
    - g_free(query);
    return NULL;
    }
    @@ -895,6 +938,7 @@
    if (pid == 0)
    {
    g_free(query);
    + g_free(query_data);
    close(out[0]);
    close(in[1]);
    @@ -911,14 +955,12 @@
    if (write(in[1], &internal_query, sizeof(internal_query)) < 0)
    purple_debug_error("dnssrv", "Could not write to TXT resolver\n");
    -
    +
    query_data->pid = pid;
    query_data->fd_out = out[0];
    query_data->fd_in = in[1];
    query_data->handle = purple_input_add(out[0], PURPLE_INPUT_READ, resolved, query_data);
    - g_free(query);
    -
    return query_data;
    #else
    if (!initialized) {
    @@ -949,39 +991,15 @@
    }
    void
    -purple_srv_cancel(PurpleSrvTxtQueryData *query_data)
    +purple_txt_cancel(PurpleSrvTxtQueryData *query_data)
    {
    - PurpleSrvTxtQueryUiOps *ops = purple_srv_txt_query_get_ui_ops();
    -
    - if (ops && ops->destroy)
    - ops->destroy(query_data);
    -
    - if (query_data->handle > 0)
    - purple_input_remove(query_data->handle);
    -#ifdef _WIN32
    - if (query_data->resolver != NULL)
    - {
    - /*
    - * It's not really possible to kill a thread. So instead we
    - * just set the callback to NULL and let the DNS lookup
    - * finish.
    - */
    - query_data->cb.srv = NULL;
    - return;
    - }
    - g_free(query_data->query);
    - g_free(query_data->error_message);
    -#else
    - close(query_data->fd_out);
    - close(query_data->fd_in);
    -#endif
    - g_free(query_data);
    + purple_srv_txt_query_destroy(query_data);
    }
    void
    -purple_txt_cancel(PurpleSrvTxtQueryData *query_data)
    +purple_srv_cancel(PurpleSrvTxtQueryData *query_data)
    {
    - purple_srv_cancel(query_data);
    + purple_srv_txt_query_destroy(query_data);
    }
    const gchar *
    @@ -1006,12 +1024,41 @@
    static void
    purple_srv_query_resolved(PurpleSrvTxtQueryData *query_data, GList *records)
    {
    + GList *l;
    + PurpleSrvResponse *records_array;
    + int i = 0, length;
    +
    g_return_if_fail(records != NULL);
    -
    - purple_debug_info("dnssrv", "SRV records resolved for %s, count: %d\n", query_data->query, g_list_length(records));
    -
    - if (query_data->cb.srv != NULL)
    - query_data->cb.srv(purple_srv_sort(records)->data, g_list_length(records), query_data->extradata);
    +
    + if (query_data->cb.srv == NULL) {
    + purple_srv_txt_query_destroy(query_data);
    +
    + while (records) {
    + g_free(records->data);
    + records = g_list_delete_link(records, records);
    + }
    + return;
    + }
    +
    + records = purple_srv_sort(records);
    + length = g_list_length(records);
    +
    + purple_debug_info("dnssrv", "SRV records resolved for %s, count: %d\n",
    + query_data->query, length);
    +
    + records_array = g_new(PurpleSrvResponse, length);
    + for (l = records; l; l = l->next, i++) {
    + records_array[i] = *(PurpleSrvResponse *)l->data;
    + }
    +
    + query_data->cb.srv(records_array, length, query_data->extradata);
    +
    + purple_srv_txt_query_destroy(query_data);
    +
    + while (records) {
    + g_free(records->data);
    + records = g_list_delete_link(records, records);
    + }
    }
    /*
    @@ -1024,19 +1071,29 @@
    purple_debug_info("dnssrv", "TXT entries resolved for %s, count: %d\n", query_data->query, g_list_length(entries));
    + /* the callback should g_free the entries.
    + */
    if (query_data->cb.txt != NULL)
    query_data->cb.txt(entries, query_data->extradata);
    + else {
    + while (entries) {
    + g_free(entries->data);
    + entries = g_list_delete_link(entries, entries);
    + }
    + }
    +
    + purple_srv_txt_query_destroy(query_data);
    }
    static void
    purple_srv_query_failed(PurpleSrvTxtQueryData *query_data, const gchar *error_message)
    {
    purple_debug_error("dnssrv", "%s\n", error_message);
    -
    +
    if (query_data->cb.srv != NULL)
    query_data->cb.srv(NULL, 0, query_data->extradata);
    -
    - purple_srv_cancel(query_data);
    +
    + purple_srv_txt_query_destroy(query_data);
    }
    static gboolean
    @@ -1069,7 +1126,7 @@
    purple_srv_txt_query_get_query(PurpleSrvTxtQueryData *query_data)
    {
    g_return_val_if_fail(query_data != NULL, NULL);
    -
    +
    return query_data->query;
    }
    @@ -1078,6 +1135,6 @@
    purple_srv_txt_query_get_type(PurpleSrvTxtQueryData *query_data)
    {
    g_return_val_if_fail(query_data != NULL, 0);
    -
    +
    return query_data->type;
    }
    --- a/libpurple/dnssrv.h Sun Aug 07 03:21:39 2011 +0000
    +++ b/libpurple/dnssrv.h Sun Aug 07 05:19:54 2011 +0000
    @@ -129,6 +129,8 @@
    * Cancel an SRV or DNS query.
    *
    * @param query_data The request to cancel.
    + *
    + * @deprecated Use purple_srv_txt_query_destroy instead
    */
    void purple_srv_cancel(PurpleSrvTxtQueryData *query_data);
    @@ -166,6 +168,8 @@
    *
    * @param query_data The request to cancel.
    * @since 2.6.0
    + *
    + * @deprecated Use purple_srv_txt_query_destroy instead
    */
    void purple_txt_cancel(PurpleSrvTxtQueryData *query_data);
    --- a/libpurple/plugins/log_reader.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/libpurple/plugins/log_reader.c Sun Aug 07 05:19:54 2011 +0000
    @@ -1454,11 +1454,15 @@
    const char *footer = NULL;
    GString *temp = NULL;
    - if ((c = strstr(c, "\n")))
    - {
    - *c = '\0';
    - c++;
    - }
    + /* There's always a trailing '\n' at the end of the file (see above), so
    + * just quit out if we don't find another, because we're at the end.
    + */
    + c = strchr(c, '\n');
    + if (!c)
    + break;
    +
    + *c = '\0';
    + c++;
    /* Convert links.
    *
    @@ -1482,14 +1486,14 @@
    char *end_paren;
    char *space;
    - if (!(end_paren = strstr(link, ")")))
    + if (!(end_paren = strchr(link, ')')))
    {
    /* Something is not as we expect. Bail out. */
    break;
    }
    if (!temp)
    - temp = g_string_sized_new(c ? (c - 1 - line) : strlen(line));
    + temp = g_string_sized_new(strlen(line));
    g_string_append_len(temp, line, (tmp - line));
    @@ -1504,7 +1508,7 @@
    /* The \r is a bit of a hack to keep there from being a \r in
    * the link text, which may not matter. */
    - if ((space = strstr(end_paren, " ")) || (space = strstr(end_paren, "\r")))
    + if ((space = strchr(end_paren, ' ')) || (space = strchr(end_paren, '\r')))
    {
    g_string_append_len(temp, end_paren + 1, space - end_paren - 1);
    @@ -1539,7 +1543,7 @@
    if (*line == '[') {
    const char *timestamp;
    - if ((timestamp = strstr(line, "]"))) {
    + if ((timestamp = strchr(line, ']'))) {
    line++;
    /* TODO: Parse the timestamp and convert it to Purple's format. */
    g_string_append(formatted, "<font size=\"2\">(");
    @@ -1658,7 +1662,7 @@
    }
    }
    } else {
    - const char *line2 = strstr(line, ":");
    + const char *line2 = strchr(line, ':');
    if (line2) {
    const char *acct_name;
    line2++;
    @@ -1819,7 +1823,7 @@
    gboolean add_new_log = FALSE;
    - if (*c) {
    + if (c && *c) {
    if (purple_str_has_prefix(c, QIP_LOG_IN_MESSAGE) ||
    purple_str_has_prefix(c, QIP_LOG_OUT_MESSAGE)) {
    @@ -1828,11 +1832,11 @@
    new_line = c;
    /* find EOL */
    - c = strstr(c, "\n");
    + c = strchr(c, '\n');
    c++;
    /* Find the last '(' character. */
    - if ((tmp = strstr(c, "\n")) != NULL) {
    + if ((tmp = strchr(c, '\n')) != NULL) {
    while (*tmp && *tmp != '(') --tmp;
    c = tmp;
    } else {
    @@ -1902,10 +1906,10 @@
    start_log = new_line;
    }
    - if (*c) {
    + if (c && *c) {
    /* find EOF */
    - c = strstr(c, "\n");
    - c++;
    + if ((c = strchr(c, '\n')))
    + c++;
    }
    }
    @@ -1983,13 +1987,13 @@
    is_in_message = purple_str_has_prefix(line, QIP_LOG_IN_MESSAGE_ESC);
    /* find EOL */
    - c = strstr(c, "\n");
    + c = strchr(c, '\n');
    /* XXX: Do we need buddy_name when we have buddy->alias? */
    buddy_name = ++c;
    /* Find the last '(' character. */
    - if ((tmp = strstr(c, "\n")) != NULL) {
    + if ((tmp = strchr(c, '\n')) != NULL) {
    while (*tmp && *tmp != '(') --tmp;
    c = tmp;
    } else {
    @@ -2042,12 +2046,12 @@
    }
    /* find EOF */
    - c = strstr(c, "\n");
    + c = strchr(c, '\n');
    line = ++c;
    }
    }
    } else {
    - if ((c = strstr(c, "\n")))
    + if ((c = strchr(c, '\n')))
    *c = '\0';
    if (line[0] != '\n' && line[0] != '\r') {
    @@ -2186,7 +2190,7 @@
    " length = (%d)\n",
    sn, data->path, data->offset, data->length);
    }
    - c = strstr(c, "\n");
    + c = strchr(c, '\n');
    c++;
    }
    @@ -2342,7 +2346,7 @@
    char *end;
    char *old_tag;
    char *tag;
    - end = strstr(start, "\n");
    + end = strchr(start, '\n');
    if (!end)
    break;
    *end = '\0';
    --- a/libpurple/prefs.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/libpurple/prefs.c Sun Aug 07 05:19:54 2011 +0000
    @@ -277,6 +277,12 @@
    }
    }
    + if ((pref_type == PURPLE_PREF_BOOLEAN || pref_type == PURPLE_PREF_INT) &&
    + pref_value == NULL) {
    + /* Missing a value attribute */
    + return;
    + }
    +
    if(purple_strequal(element_name, "item")) {
    struct purple_pref *pref;
    --- a/libpurple/protocols/gg/gg.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/libpurple/protocols/gg/gg.c Sun Aug 07 05:19:54 2011 +0000
    @@ -487,15 +487,16 @@
    GGPSearchForm *form = user_data;
    guint32 seq;
    - g_free(form->offset);
    - form->offset = g_strdup(form->last_uin);
    + form->page_number++;
    ggp_search_remove(info->searches, form->seq);
    - purple_debug_info("gg", "ggp_callback_show_next(): Removed seq %u", form->seq);
    + purple_debug_info("gg", "ggp_callback_show_next(): Removed seq %u\n",
    + form->seq);
    seq = ggp_search_start(gc, form);
    ggp_search_add(info->searches, seq, form);
    - purple_debug_info("gg", "ggp_callback_show_next(): Added seq %u", seq);
    + purple_debug_info("gg", "ggp_callback_show_next(): Added seq %u\n",
    + seq);
    }
    static void ggp_callback_add_buddy(PurpleConnection *gc, GList *row, gpointer user_data)
    @@ -526,21 +527,16 @@
    form = ggp_search_form_new(GGP_SEARCH_TYPE_FULL);
    form->user_data = info;
    - form->lastname = charset_convert(
    - purple_request_fields_get_string(fields, "lastname"),
    - "UTF-8", "CP1250");
    - form->firstname = charset_convert(
    - purple_request_fields_get_string(fields, "firstname"),
    - "UTF-8", "CP1250");
    - form->nickname = charset_convert(
    - purple_request_fields_get_string(fields, "nickname"),
    - "UTF-8", "CP1250");
    - form->city = charset_convert(
    - purple_request_fields_get_string(fields, "city"),
    - "UTF-8", "CP1250");
    - form->birthyear = charset_convert(
    - purple_request_fields_get_string(fields, "year"),
    - "UTF-8", "CP1250");
    + form->lastname = g_strdup(
    + purple_request_fields_get_string(fields, "lastname"));
    + form->firstname = g_strdup(
    + purple_request_fields_get_string(fields, "firstname"));
    + form->nickname = g_strdup(
    + purple_request_fields_get_string(fields, "nickname"));
    + form->city = g_strdup(
    + purple_request_fields_get_string(fields, "city"));
    + form->birthyear = g_strdup(
    + purple_request_fields_get_string(fields, "year"));
    switch (purple_request_fields_get_choice(fields, "gender")) {
    case 1:
    @@ -557,11 +553,10 @@
    form->active = purple_request_fields_get_bool(fields, "active")
    ? g_strdup(GG_PUBDIR50_ACTIVE_TRUE) : NULL;
    - form->offset = g_strdup("0");
    -
    seq = ggp_search_start(gc, form);
    ggp_search_add(info->searches, seq, form);
    - purple_debug_info("gg", "ggp_callback_find_buddies(): Added seq %u", seq);
    + purple_debug_info("gg", "ggp_callback_find_buddies(): Added seq %u\n",
    + seq);
    }
    static void ggp_find_buddies(PurplePluginAction *action)
    @@ -882,30 +877,26 @@
    /* ----- BLOCK BUDDIES -------------------------------------------------- */
    -static void ggp_bmenu_block(PurpleBlistNode *node, gpointer ignored)
    +static void ggp_add_deny(PurpleConnection *gc, const char *who)
    {
    - PurpleConnection *gc;
    - PurpleBuddy *buddy;
    - GGPInfo *info;
    - uin_t uin;
    -
    - buddy = (PurpleBuddy *)node;
    - gc = purple_account_get_connection(purple_buddy_get_account(buddy));
    - info = gc->proto_data;
    -
    - uin = ggp_str_to_uin(purple_buddy_get_name(buddy));
    -
    - if (purple_blist_node_get_bool(node, "blocked")) {
    - purple_blist_node_set_bool(node, "blocked", FALSE);
    - gg_remove_notify_ex(info->session, uin, GG_USER_BLOCKED);
    - gg_add_notify_ex(info->session, uin, GG_USER_NORMAL);
    - purple_debug_info("gg", "send: uin=%d; mode=NORMAL\n", uin);
    - } else {
    - purple_blist_node_set_bool(node, "blocked", TRUE);
    - gg_remove_notify_ex(info->session, uin, GG_USER_NORMAL);
    - gg_add_notify_ex(info->session, uin, GG_USER_BLOCKED);
    - purple_debug_info("gg", "send: uin=%d; mode=BLOCKED\n", uin);
    - }
    + GGPInfo *info = gc->proto_data;
    + uin_t uin = ggp_str_to_uin(who);
    +
    + purple_debug_info("gg", "ggp_add_deny: %u\n", uin);
    +
    + gg_remove_notify_ex(info->session, uin, GG_USER_NORMAL);
    + gg_add_notify_ex(info->session, uin, GG_USER_BLOCKED);
    +}
    +
    +static void ggp_rem_deny(PurpleConnection *gc, const char *who)
    +{
    + GGPInfo *info = gc->proto_data;
    + uin_t uin = ggp_str_to_uin(who);
    +
    + purple_debug_info("gg", "ggp_rem_deny: %u\n", uin);
    +
    + gg_remove_notify_ex(info->session, uin, GG_USER_BLOCKED);
    + gg_add_notify_ex(info->session, uin, GG_USER_NORMAL);
    }
    /* ---------------------------------------------------------------------- */
    @@ -927,6 +918,9 @@
    PurpleBuddy *buddy;
    gpointer buddy_icon_data;
    + purple_debug_info("gg", "gg_fetch_avatar_cb: got avatar image for %s\n",
    + d->uin);
    +
    /* FIXME: This shouldn't be necessary */
    if (!PURPLE_CONNECTION_IS_VALID(d->gc)) {
    g_free(d->uin);
    @@ -945,7 +939,8 @@
    purple_buddy_icons_set_for_user(account, purple_buddy_get_name(buddy),
    buddy_icon_data, len, d->avatar_url);
    - purple_debug_info("gg", "UIN: %s should have avatar now\n", d->uin);
    + purple_debug_info("gg", "gg_fetch_avatar_cb: UIN %s should have avatar "
    + "now\n", d->uin);
    out:
    g_free(d->uin);
    @@ -1027,6 +1022,8 @@
    data->uin = g_strdup(uin);
    data->avatar_url = g_strdup(bigavatar);
    + purple_debug_info("gg", "gg_get_avatar_url_cb: "
    + "requesting avatar for %s\n", uin);
    url_data = purple_util_fetch_url_request_len_with_account(account,
    bigavatar, TRUE, "Mozilla/4.0 (compatible; MSIE 5.0)",
    FALSE, NULL, FALSE, -1, gg_fetch_avatar_cb, data);
    @@ -1076,6 +1073,7 @@
    {
    gchar *from;
    const char *st;
    + char *status_msg = NULL;
    ggp_update_buddy_avatar(gc, uin);
    @@ -1113,14 +1111,24 @@
    break;
    }
    - purple_debug_info("gg", "st = %s\n", st);
    - //msg = charset_convert(descr, "CP1250", "UTF-8");
    - if (descr == NULL) {
    + if (descr != NULL) {
    + status_msg = g_strdup(descr);
    + g_strstrip(status_msg);
    + if (status_msg[0] == '\0') {
    + g_free(status_msg);
    + status_msg = NULL;
    + }
    + }
    +
    + purple_debug_info("gg", "status of %u is %s [%s]\n", uin, st,
    + status_msg ? status_msg : "");
    + if (status_msg == NULL) {
    purple_prpl_got_user_status(purple_connection_get_account(gc),
    - from, st, NULL);
    + from, st, NULL);
    } else {
    purple_prpl_got_user_status(purple_connection_get_account(gc),
    - from, st, "message", descr, NULL);
    + from, st, "message", status_msg, NULL);
    + g_free(status_msg);
    }
    g_free(from);
    }
    @@ -1131,7 +1139,8 @@
    GGPInfo *info = form->user_data;
    ggp_search_remove(info->searches, form->seq);
    - purple_debug_info("gg", "ggp_sr_close_cb(): Removed seq %u", form->seq);
    + purple_debug_info("gg", "ggp_sr_close_cb(): Removed seq %u\n",
    + form->seq);
    ggp_search_form_destroy(form);
    }
    @@ -1246,6 +1255,8 @@
    res_count = gg_pubdir50_count(req);
    res_count = (res_count > PUBDIR_RESULTS_MAX) ? PUBDIR_RESULTS_MAX : res_count;
    + if (form->page_size == 0)
    + form->page_size = res_count;
    results = purple_notify_searchresults_new();
    @@ -1255,7 +1266,8 @@
    purple_notify_error(gc, NULL,
    _("Unable to display the search results."),
    NULL);
    - ggp_sr_close_cb(form);
    + if (form->window == NULL)
    + ggp_sr_close_cb(form);
    return;
    }
    @@ -1297,11 +1309,6 @@
    (birth && strncmp(birth, "0", 1)) ? birth : g_strdup("-"));
    purple_notify_searchresults_row_add(results, row);
    -
    - if (i == res_count - 1) {
    - g_free(form->last_uin);
    - form->last_uin = ggp_search_get_result(req, i, GG_PUBDIR50_UIN);
    - }
    }
    purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_CONTINUE,
    @@ -1342,7 +1349,8 @@
    seq = gg_pubdir50_seq(req);
    form = ggp_search_get(info->searches, seq);
    - purple_debug_info("gg", "ggp_pubdir_reply_handler(): seq %u --> form %p", seq, form);
    + purple_debug_info("gg",
    + "ggp_pubdir_reply_handler(): seq %u --> form %p\n", seq, form);
    /*
    * this can happen when user will request more results
    * and close the results window before they arrive.
    @@ -1355,7 +1363,8 @@
    purple_notify_error(gc, NULL,
    _("No matching users found"),
    _("There are no users matching your search criteria."));
    - ggp_sr_close_cb(form);
    + if (form->window == NULL)
    + ggp_sr_close_cb(form);
    return;
    }
    @@ -1914,6 +1923,17 @@
    PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
    _("Connection failed"));
    break;
    + case GG_EVENT_MSG:
    + if (ev->event.msg.sender == 0)
    + /* system messages are mostly ads */
    + purple_debug_info("gg", "System message:\n%s\n",
    + ev->event.msg.message);
    + else
    + purple_debug_warning("gg", "GG_EVENT_MSG: message from user %u "
    + "unexpected while connecting:\n%s\n",
    + ev->event.msg.sender,
    + ev->event.msg.message);
    + break;
    default:
    purple_debug_error("gg", "strange event: %d\n", ev->type);
    break;
    @@ -1938,23 +1958,18 @@
    char *text;
    char *tmp;
    - status = purple_presence_get_active_status(purple_buddy_get_presence(b));
    -
    + status = purple_presence_get_active_status(
    + purple_buddy_get_presence(b));
    msg = purple_status_get_attr_string(status, "message");
    - if (msg != NULL) {
    - tmp = purple_markup_strip_html(msg);
    - text = g_markup_escape_text(tmp, -1);
    - g_free(tmp);
    -
    - return text;
    - } else {
    - tmp = purple_utf8_salvage(purple_status_get_name(status));
    - text = g_markup_escape_text(tmp, -1);
    - g_free(tmp);
    -
    - return text;
    - }
    + if (msg == NULL)
    + return NULL;
    +
    + tmp = purple_markup_strip_html(msg);
    + text = g_markup_escape_text(tmp, -1);
    + g_free(tmp);
    +
    + return text;
    }
    static void ggp_tooltip_text(PurpleBuddy *b, PurpleNotifyUserInfo *user_info, gboolean full)
    @@ -2061,20 +2076,6 @@
    m = g_list_append(m, act);
    }
    - /* Using a blist node boolean here is also wrong.
    - * Once the Block and Unblock actions are added to the core,
    - * this will have to go. -- rlaager */
    - if (purple_blist_node_get_bool(node, "blocked")) {
    - act = purple_menu_action_new(_("Unblock"),
    - PURPLE_CALLBACK(ggp_bmenu_block),
    - NULL, NULL);
    - } else {
    - act = purple_menu_action_new(_("Block"),
    - PURPLE_CALLBACK(ggp_bmenu_block),
    - NULL, NULL);
    - }
    - m = g_list_append(m, act);
    -
    return m;
    }
    @@ -2371,8 +2372,6 @@
    form->user_data = info;
    form->uin = g_strdup(name);
    - form->offset = g_strdup("0");
    - form->last_uin = g_strdup("0");
    seq = ggp_search_start(gc, form);
    ggp_search_add(info->searches, seq, form);
    @@ -2673,9 +2672,9 @@
    ggp_remove_buddy, /* remove_buddy */
    NULL, /* remove_buddies */
    NULL, /* add_permit */
    - NULL, /* add_deny */
    + ggp_add_deny, /* add_deny */
    NULL, /* rem_permit */
    - NULL, /* rem_deny */
    + ggp_rem_deny, /* rem_deny */
    NULL, /* set_permit_deny */
    ggp_join_chat, /* join_chat */
    NULL, /* reject_chat */
    --- a/libpurple/protocols/gg/lib/libgadu.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/libpurple/protocols/gg/lib/libgadu.c Sun Aug 07 05:19:54 2011 +0000
    @@ -893,8 +893,7 @@
    gnutls_global_init();
    gnutls_certificate_allocate_credentials(&tmp->xcred);
    gnutls_init(&tmp->session, GNUTLS_CLIENT);
    - gnutls_priority_set_direct(tmp->session, "NORMAL:-VERS-TLS", NULL);
    -// gnutls_priority_set_direct(tmp->session, "NONE:+VERS-SSL3.0:+AES-128-CBC:+RSA:+SHA1:+COMP-NULL", NULL);
    + gnutls_set_default_priority(tmp->session);
    gnutls_credentials_set(tmp->session, GNUTLS_CRD_CERTIFICATE, tmp->xcred);
    #elif defined(GG_CONFIG_HAVE_OPENSSL)
    char buf[1024];
    --- a/libpurple/protocols/gg/lib/libgadu.h Sun Aug 07 03:21:39 2011 +0000
    +++ b/libpurple/protocols/gg/lib/libgadu.h Sun Aug 07 05:19:54 2011 +0000
    @@ -1442,6 +1442,7 @@
    int gg_file_hash_sha1(int fd, uint8_t *result) GG_DEPRECATED;
    +#undef printf
    #ifdef __GNUC__
    char *gg_saprintf(const char *format, ...) __attribute__ ((format (printf, 1, 2))) GG_DEPRECATED;
    #else
    --- a/libpurple/protocols/gg/lib/resolver.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/libpurple/protocols/gg/lib/resolver.c Sun Aug 07 05:19:54 2011 +0000
    @@ -249,6 +249,7 @@
    #endif /* GG_CONFIG_HAVE_GETHOSTBYNAME_R */
    }
    +#if defined(GG_CONFIG_HAVE_PTHREAD) || !defined(_WIN32)
    /**
    * \internal Rozwiązuje nazwę i zapisuje wynik do podanego desktyptora.
    *
    @@ -286,6 +287,7 @@
    return res;
    }
    +#endif
    /**
    * \internal Odpowiednik \c gethostbyname zapewniający współbieżność.
    --- a/libpurple/protocols/gg/search.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/libpurple/protocols/gg/search.c Sun Aug 07 05:19:54 2011 +0000
    @@ -38,6 +38,8 @@
    form->window = NULL;
    form->user_data = NULL;
    form->seq = 0;
    + form->page_number = 0;
    + form->page_size = 0;
    form->uin = NULL;
    form->lastname = NULL;
    @@ -47,8 +49,6 @@
    form->birthyear = NULL;
    form->gender = NULL;
    form->active = NULL;
    - form->offset = NULL;
    - form->last_uin = NULL;
    return form;
    }
    @@ -62,6 +62,8 @@
    form->window = NULL;
    form->user_data = NULL;
    form->seq = 0;
    + form->page_number = 0;
    + form->page_size = 0;
    g_free(form->uin);
    g_free(form->lastname);
    @@ -71,8 +73,6 @@
    g_free(form->birthyear);
    g_free(form->gender);
    g_free(form->active);
    - g_free(form->offset);
    - g_free(form->last_uin);
    g_free(form);
    }
    /* }}} */
    @@ -137,7 +137,7 @@
    {
    GGPInfo *info = gc->proto_data;
    gg_pubdir50_t req;
    - guint seq;
    + guint seq, offset;
    purple_debug_info("gg", "It's time to perform a search...\n");
    @@ -187,8 +187,10 @@
    }
    }
    - purple_debug_info("gg", "offset: %s\n", form->offset);
    - gg_pubdir50_add(req, GG_PUBDIR50_START, g_strdup(form->offset));
    + offset = form->page_size * form->page_number;
    + purple_debug_info("gg", "page number: %u, page size: %u, offset: %u\n",
    + form->page_number, form->page_size, offset);
    + gg_pubdir50_add(req, GG_PUBDIR50_START, g_strdup_printf("%u", offset));
    if ((seq = gg_pubdir50(info->session, req)) == 0) {
    purple_debug_warning("gg", "ggp_bmenu_show_details: Search failed.\n");
    --- a/libpurple/protocols/gg/search.h Sun Aug 07 03:21:39 2011 +0000
    +++ b/libpurple/protocols/gg/search.h Sun Aug 07 05:19:54 2011 +0000
    @@ -46,12 +46,11 @@
    char *birthyear;
    char *gender;
    char *active;
    - char *offset;
    -
    - char *last_uin;
    GGPSearchType search_type;
    guint32 seq;
    + guint16 page_number;
    + guint16 page_size; /* how many contacts fits into one page of results */
    void *user_data;
    void *window;
    --- a/libpurple/protocols/irc/irc.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/libpurple/protocols/irc/irc.c Sun Aug 07 05:19:54 2011 +0000
    @@ -101,7 +101,11 @@
    static int irc_send_raw(PurpleConnection *gc, const char *buf, int len)
    {
    struct irc_conn *irc = (struct irc_conn*)gc->proto_data;
    - return do_send(irc, buf, len);
    + if (len == -1) {
    + len = strlen(buf);
    + }
    + irc_send_len(irc, buf, len);
    + return len;
    }
    static void
    @@ -144,16 +148,18 @@
    int irc_send(struct irc_conn *irc, const char *buf)
    {
    - int ret, buflen;
    + return irc_send_len(irc, buf, strlen(buf));
    +}
    +
    +int irc_send_len(struct irc_conn *irc, const char *buf, int buflen)
    +{
    + int ret;
    char *tosend= g_strdup(buf);
    purple_signal_emit(_irc_plugin, "irc-sending-text", purple_account_get_connection(irc->account), &tosend);
    if (tosend == NULL)
    return 0;
    - buflen = strlen(tosend);
    -
    -
    /* If we're not buffering writes, try to send immediately */
    if (!irc->writeh)
    ret = do_send(irc, tosend, buflen);
    @@ -245,10 +251,14 @@
    static void irc_who_channel(PurpleConversation *conv, struct irc_conn *irc)
    {
    - if (purple_conversation_get_account(conv) == irc->account && purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) {
    + if (purple_conversation_get_account(conv) == irc->account
    + && purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT
    + && !purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv))) {
    char *buf = irc_format(irc, "vc", "WHO", purple_conversation_get_name(conv));
    - purple_debug(PURPLE_DEBUG_INFO, "irc", "Performing periodic who on %s", purple_conversation_get_name(conv));
    + purple_debug(PURPLE_DEBUG_INFO, "irc",
    + "Performing periodic who on %s\n",
    + purple_conversation_get_name(conv));
    irc_send(irc, buf);
    g_free(buf);
    }
    --- a/libpurple/protocols/irc/irc.h Sun Aug 07 03:21:39 2011 +0000
    +++ b/libpurple/protocols/irc/irc.h Sun Aug 07 05:19:54 2011 +0000
    @@ -106,6 +106,7 @@
    typedef int (*IRCCmdCallback) (struct irc_conn *irc, const char *cmd, const char *target, const char **args);
    int irc_send(struct irc_conn *irc, const char *buf);
    +int irc_send_len(struct irc_conn *irc, const char *buf, int len);
    gboolean irc_blist_timeout(struct irc_conn *irc);
    gboolean irc_who_channel_timeout(struct irc_conn *irc);
    void irc_buddy_query(struct irc_conn *irc);
    --- a/libpurple/protocols/irc/msgs.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/libpurple/protocols/irc/msgs.c Sun Aug 07 05:19:54 2011 +0000
    @@ -445,7 +445,7 @@
    g_free(userhost);
    g_free(realname);
    - flags = purple_conv_chat_user_get_flags(chat, cb->name);
    + flags = cb->flags;
    if (args[6][0] == 'G' && !(flags & PURPLE_CBFLAGS_AWAY)) {
    purple_conv_chat_user_set_flags(chat, cb->name, flags | PURPLE_CBFLAGS_AWAY);
    --- a/libpurple/protocols/irc/parse.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/libpurple/protocols/irc/parse.c Sun Aug 07 05:19:54 2011 +0000
    @@ -459,6 +459,7 @@
    decoded = g_string_append(decoded, "</U>");
    if (font)
    decoded = g_string_append(decoded, "</FONT>");
    + bold = italic = underline = font = FALSE;
    break;
    default:
    purple_debug(PURPLE_DEBUG_ERROR, "irc", "Unexpected mIRC formatting character %d\n", *cur);
    --- a/libpurple/protocols/jabber/jabber.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/libpurple/protocols/jabber/jabber.c Sun Aug 07 05:19:54 2011 +0000
    @@ -579,7 +579,7 @@
    */
    jabber_send_raw(js, buf, len);
    - return len;
    + return (len < 0 ? strlen(buf) : len);
    }
    void jabber_send_signal_cb(PurpleConnection *pc, xmlnode **packet,
    @@ -3009,7 +3009,7 @@
    {
    JabberChat *chat = jabber_chat_find_by_conv(conv);
    GHashTable *components;
    - JabberID *jid;
    + JabberID *jid = NULL;
    const char *room = NULL, *server = NULL, *handle = NULL;
    if (!chat || !args || !args[0])
    @@ -3017,7 +3017,8 @@
    components = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
    - jid = jabber_id_new(args[0]);
    + if (strchr(args[0], '@'))
    + jid = jabber_id_new(args[0]);
    if (jid) {
    room = jid->node;
    server = jid->domain;
    @@ -3673,8 +3674,7 @@
    PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
    PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
    jabber_cmd_chat_join,
    - _("join: &lt;room&gt; [password]: Join a chat on this server."),
    - /* _("join: &lt;room[@server]&gt; [password]: Join a chat."), */
    + _("join: &lt;room[@server]&gt; [password]: Join a chat."),
    NULL);
    commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
    --- a/libpurple/protocols/jabber/usermood.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/libpurple/protocols/jabber/usermood.c Sun Aug 07 05:19:54 2011 +0000
    @@ -177,12 +177,12 @@
    xmlnode_set_attrib(publish,"node","http://jabber.org/protocol/mood");
    moodnode = xmlnode_new_child(xmlnode_new_child(publish, "item"), "mood");
    xmlnode_set_namespace(moodnode, "http://jabber.org/protocol/mood");
    - if (mood) {
    + if (mood && *mood) {
    /* if mood is NULL, set an empty mood node, meaning: unset mood */
    xmlnode_new_child(moodnode, mood);
    }
    - if (text && text[0] != '\0') {
    + if (text && *text) {
    xmlnode *textnode = xmlnode_new_child(moodnode, "text");
    xmlnode_insert_data(textnode, text, -1);
    }
    @@ -195,4 +195,4 @@
    PurpleMood *jabber_get_moods(PurpleAccount *account)
    {
    return moods;
    -}
    \ No newline at end of file
    +}
    --- a/libpurple/protocols/msn/notification.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/libpurple/protocols/msn/notification.c Sun Aug 07 05:19:54 2011 +0000
    @@ -198,6 +198,9 @@
    {
    /* RPS authentication */
    + if (session->nexus)
    + msn_nexus_destroy(session->nexus);
    +
    session->nexus = msn_nexus_new(session);
    session->nexus->policy = g_strdup(cmd->params[3]);
    --- a/libpurple/protocols/msn/slp.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/libpurple/protocols/msn/slp.c Sun Aug 07 05:19:54 2011 +0000
    @@ -284,7 +284,6 @@
    purple_xfer_unref(xfer);
    slpmsg = msn_slpmsg_file_new(slpcall, purple_xfer_get_size(xfer));
    - msn_slpmsg_set_slplink(slpmsg, slpcall->slplink);
    msn_slplink_send_slpmsg(slpcall->slplink, slpmsg);
    }
    --- a/libpurple/protocols/msn/slpcall.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/libpurple/protocols/msn/slpcall.c Sun Aug 07 05:19:54 2011 +0000
    @@ -490,12 +490,10 @@
    if (img != NULL) {
    /* DATA PREP */
    slpmsg = msn_slpmsg_dataprep_new(slpcall);
    - msn_slpmsg_set_slplink(slpmsg, slplink);
    msn_slplink_queue_slpmsg(slplink, slpmsg);
    /* DATA */
    slpmsg = msn_slpmsg_obj_new(slpcall, img);
    - msn_slpmsg_set_slplink(slpmsg, slplink);
    msn_slplink_queue_slpmsg(slplink, slpmsg);
    purple_imgstore_unref(img);
    --- a/libpurple/protocols/oscar/family_feedbag.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/libpurple/protocols/oscar/family_feedbag.c Sun Aug 07 05:19:54 2011 +0000
    @@ -127,7 +127,8 @@
    {
    g_string_append_printf(str,
    "%s gid=0x%04hx, bid=0x%04hx, list_type=0x%04hx [%s], name=%s.\n",
    - prefix, item->gid, item->bid, item->type, aim_ssi_type_to_string(item->type), item->name);
    + prefix, item->gid, item->bid, item->type, aim_ssi_type_to_string(item->type),
    + item->name ? item->name : "(null)");
    }
    /**
    --- a/libpurple/protocols/oscar/oscar.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/libpurple/protocols/oscar/oscar.c Sun Aug 07 05:19:54 2011 +0000
    @@ -743,11 +743,15 @@
    gc->flags |= PURPLE_CONNECTION_HTML;
    if (g_str_equal(purple_account_get_protocol_id(account), "prpl-icq")) {
    od->icq = TRUE;
    - gc->flags |= PURPLE_CONNECTION_SUPPORT_MOODS;
    } else {
    gc->flags |= PURPLE_CONNECTION_AUTO_RESP;
    }
    + /* Set this flag based on the protocol_id rather than the username,
    + because that is what's tied to the get_moods prpl callback. */
    + if (g_str_equal(purple_account_get_protocol_id(account), "prpl-icq"))
    + gc->flags |= PURPLE_CONNECTION_SUPPORT_MOODS;
    +
    od->default_port = purple_account_get_int(account, "port", OSCAR_DEFAULT_LOGIN_PORT);
    encryption_type = purple_account_get_string(account, "encryption", OSCAR_DEFAULT_ENCRYPTION);
    @@ -1465,10 +1469,10 @@
    } else if (previous_status != NULL && purple_status_is_available(previous_status)) {
    itmsurl = g_strdup(purple_status_get_attr_string(previous_status, "itmsurl"));
    }
    - purple_debug_info("oscar", "Activating status '%s' for buddy %s, message = '%s', itmsurl = '%s'\n", status_id, info->bn, message, itmsurl);
    + purple_debug_info("oscar", "Activating status '%s' for buddy %s, message = '%s', itmsurl = '%s'\n", status_id, info->bn, message ? message : "(null)", itmsurl ? itmsurl : "(null)");
    purple_prpl_got_user_status(account, info->bn, status_id, "message", message, "itmsurl", itmsurl, NULL);
    } else {
    - purple_debug_info("oscar", "Activating status '%s' for buddy %s, message = '%s'\n", status_id, info->bn, message);
    + purple_debug_info("oscar", "Activating status '%s' for buddy %s, message = '%s'\n", status_id, info->bn, message ? message : "(null)");
    purple_prpl_got_user_status(account, info->bn, status_id, "message", message, NULL);
    }
    --- a/libpurple/protocols/sametime/sametime.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/libpurple/protocols/sametime/sametime.c Sun Aug 07 05:19:54 2011 +0000
    @@ -922,6 +922,11 @@
    alias = mwSametimeGroup_getAlias(stgroup);
    type = mwSametimeGroup_getType(stgroup);
    + if (!name) {
    + DEBUG_WARN("Can't ensure a null group\n");
    + return;
    + }
    +
    DEBUG_INFO("attempting to ensure group %s, called %s\n",
    NSTR(name), NSTR(alias));
    --- a/libpurple/protocols/simple/simple.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/libpurple/protocols/simple/simple.c Sun Aug 07 05:19:54 2011 +0000
    @@ -1932,6 +1932,8 @@
    gc->proto_data = sip = g_new0(struct simple_account_data, 1);
    sip->gc = gc;
    + sip->fd = -1;
    + sip->listenfd = -1;
    sip->account = account;
    sip->registerexpire = 900;
    sip->udp = purple_account_get_bool(account, "udp", FALSE);
    --- a/libpurple/protocols/yahoo/libymsg.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/libpurple/protocols/yahoo/libymsg.c Sun Aug 07 05:19:54 2011 +0000
    @@ -3567,11 +3567,10 @@
    status_id = purple_status_get_id(status);
    msg = purple_status_get_attr_string(status, "message");
    - if (!strcmp(status_id, YAHOO_STATUS_TYPE_AVAILABLE)) {
    - if ((msg != NULL) && (*msg != '\0'))
    - return YAHOO_STATUS_CUSTOM;
    - else
    - return YAHOO_STATUS_AVAILABLE;
    + if ((msg != NULL) && (*msg != '\0')) {
    + return YAHOO_STATUS_CUSTOM;
    + } else if (!strcmp(status_id, YAHOO_STATUS_TYPE_AVAILABLE)) {
    + return YAHOO_STATUS_AVAILABLE;
    } else if (!strcmp(status_id, YAHOO_STATUS_TYPE_BRB)) {
    return YAHOO_STATUS_BRB;
    } else if (!strcmp(status_id, YAHOO_STATUS_TYPE_BUSY)) {
    @@ -4849,6 +4848,11 @@
    if (idle)
    yahoo_packet_hash_str(pkt, 47, "2");
    + else if (yd->current_status == YAHOO_STATUS_CUSTOM &&
    + !purple_status_is_available(status))
    + /* We are still unavailable in this case.
    + * Make sure Yahoo knows that */
    + yahoo_packet_hash_str(pkt, 47, "1");
    yahoo_packet_send_and_free(pkt, yd);
    @@ -4876,7 +4880,10 @@
    type = purple_status_type_new(PURPLE_STATUS_AWAY, YAHOO_STATUS_TYPE_BRB, _("Be Right Back"), TRUE);
    types = g_list_append(types, type);
    - type = purple_status_type_new(PURPLE_STATUS_UNAVAILABLE, YAHOO_STATUS_TYPE_BUSY, _("Busy"), TRUE);
    + type = purple_status_type_new_with_attrs(PURPLE_STATUS_UNAVAILABLE, YAHOO_STATUS_TYPE_BUSY,
    + _("Busy"), TRUE, TRUE, FALSE,
    + "message", _("Message"),
    + purple_value_new(PURPLE_TYPE_STRING), NULL);
    types = g_list_append(types, type);
    type = purple_status_type_new(PURPLE_STATUS_AWAY, YAHOO_STATUS_TYPE_NOTATHOME, _("Not at Home"), TRUE);
    --- a/libpurple/protocols/yahoo/libymsg.h Sun Aug 07 03:21:39 2011 +0000
    +++ b/libpurple/protocols/yahoo/libymsg.h Sun Aug 07 05:19:54 2011 +0000
    @@ -48,7 +48,7 @@
    #define YAHOO_ROOMLIST_LOCALE "us"
    /* Yahoo! JAPAN stuff */
    -#define YAHOOJP_PAGER_HOST_REQ_URL "http://cs1.msg.vip.ogk.yahoo.co.jp/capacity"
    +#define YAHOOJP_PAGER_HOST_REQ_URL "http://cs1.yahoo.co.jp/capacity"
    #define YAHOOJP_TOKEN_URL "https://login.yahoo.co.jp/config/pwtoken_get?src=ymsgr&ts=&login=%s&passwd=%s&chal=%s"
    #define YAHOOJP_LOGIN_URL "https://login.yahoo.co.jp/config/pwtoken_login?src=ymsgr&ts=&token=%s"
    #define YAHOOJP_PROFILE_URL "http://profiles.yahoo.co.jp/"
    --- a/libpurple/tests/Makefile.am Sun Aug 07 03:21:39 2011 +0000
    +++ b/libpurple/tests/Makefile.am Sun Aug 07 05:19:54 2011 +0000
    @@ -17,6 +17,7 @@
    test_oscar_util.c \
    test_yahoo_util.c \
    test_util.c \
    + test_xmlnode.c \
    $(top_builddir)/libpurple/util.h
    check_libpurple_CFLAGS=\
    --- a/libpurple/tests/check_libpurple.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/libpurple/tests/check_libpurple.c Sun Aug 07 05:19:54 2011 +0000
    @@ -91,6 +91,7 @@
    srunner_add_suite(sr, oscar_util_suite());
    srunner_add_suite(sr, yahoo_util_suite());
    srunner_add_suite(sr, util_suite());
    + srunner_add_suite(sr, xmlnode_suite());
    /* make this a libpurple "ui" */
    purple_check_init();
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/libpurple/tests/test_xmlnode.c Sun Aug 07 05:19:54 2011 +0000
    @@ -0,0 +1,34 @@
    +#include <string.h>
    +
    +#include "tests.h"
    +#include "../xmlnode.h"
    +
    +/*
    + * If we really wanted to test the billion laughs attack we would
    + * need to have more than just 4 ha's. But as long as this shorter
    + * document fails to parse, the longer one should also fail to parse.
    + */
    +START_TEST(test_xmlnode_billion_laughs_attack)
    +{
    + const char *malicious_xml_doc = "<!DOCTYPE root [ <!ENTITY ha \"Ha !\"><!ENTITY ha2 \"&ha; &ha;\"><!ENTITY ha3 \"&ha2; &ha2;\"> ]><root>&ha3;</root>";
    +
    + /* Uncomment this line if you want to see the error message given by
    + the parser for the above XML document */
    + /* purple_debug_set_enabled(TRUE); */
    +
    + fail_if(xmlnode_from_str(malicious_xml_doc, -1),
    + "xmlnode_from_str() returned an XML tree, but we didn't want it to");
    +}
    +END_TEST
    +
    +Suite *
    +xmlnode_suite(void)
    +{
    + Suite *s = suite_create("Utility Functions");
    +
    + TCase *tc = tcase_create("xmlnode");
    + tcase_add_test(tc, test_xmlnode_billion_laughs_attack);
    + suite_add_tcase(s, tc);
    +
    + return s;
    +}
    --- a/libpurple/tests/tests.h Sun Aug 07 03:21:39 2011 +0000
    +++ b/libpurple/tests/tests.h Sun Aug 07 05:19:54 2011 +0000
    @@ -16,6 +16,7 @@
    Suite * oscar_util_suite(void);
    Suite * yahoo_util_suite(void);
    Suite * util_suite(void);
    +Suite * xmlnode_suite(void);
    /* helper macros */
    #define assert_int_equal(expected, actual) { \
    --- a/libpurple/upnp.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/libpurple/upnp.c Sun Aug 07 05:19:54 2011 +0000
    @@ -535,7 +535,7 @@
    dd->retry_count++;
    purple_upnp_discover_send_broadcast(dd);
    } else {
    - if (dd->fd)
    + if (dd->fd != -1)
    close(dd->fd);
    control_info.status = PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER;
    @@ -662,7 +662,7 @@
    }
    /* Set up the sockets */
    - sock = socket(AF_INET, SOCK_DGRAM, 0);
    + dd->fd = sock = socket(AF_INET, SOCK_DGRAM, 0);
    if(sock == -1) {
    purple_debug_error("upnp",
    "purple_upnp_discover(): Failed In sock creation\n");
    @@ -672,8 +672,6 @@
    return;
    }
    - dd->fd = sock;
    -
    /* TODO: Non-blocking! */
    if((hp = gethostbyname(HTTPMU_HOST_ADDRESS)) == NULL) {
    purple_debug_error("upnp",
    @@ -820,7 +818,7 @@
    static void
    looked_up_internal_ip_cb(gpointer data, gint source, const gchar *error_message)
    {
    - if (source) {
    + if (source != -1) {
    strncpy(control_info.internalip,
    purple_network_get_local_system_ip(source),
    sizeof(control_info.internalip));
    --- a/libpurple/value.h Sun Aug 07 03:21:39 2011 +0000
    +++ b/libpurple/value.h Sun Aug 07 05:19:54 2011 +0000
    @@ -79,7 +79,8 @@
    PURPLE_SUBTYPE_XMLNODE,
    PURPLE_SUBTYPE_USERINFO,
    PURPLE_SUBTYPE_STORED_IMAGE,
    - PURPLE_SUBTYPE_CERTIFICATEPOOL
    + PURPLE_SUBTYPE_CERTIFICATEPOOL,
    + PURPLE_SUBTYPE_CHATBUDDY
    } PurpleSubType;
    /**
    --- a/libpurple/xmlnode.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/libpurple/xmlnode.c Sun Aug 07 05:19:54 2011 +0000
    @@ -62,7 +62,7 @@
    xmlnode*
    xmlnode_new(const char *name)
    {
    - g_return_val_if_fail(name != NULL, NULL);
    + g_return_val_if_fail(name != NULL && *name != '\0', NULL);
    return new_node(name, XMLNODE_TYPE_TAG);
    }
    @@ -73,7 +73,7 @@
    xmlnode *node;
    g_return_val_if_fail(parent != NULL, NULL);
    - g_return_val_if_fail(name != NULL, NULL);
    + g_return_val_if_fail(name != NULL && *name != '\0', NULL);
    node = new_node(name, XMLNODE_TYPE_TAG);
    --- a/pidgin/gtkaccount.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/pidgin/gtkaccount.c Sun Aug 07 05:19:54 2011 +0000
    @@ -2135,7 +2135,7 @@
    gtk_list_store_clear(dialog->model);
    if ((path = purple_prefs_get_path(PIDGIN_PREFS_ROOT "/accounts/buddyicon")) != NULL) {
    - GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(path, NULL);
    + GdkPixbuf *pixbuf = pidgin_pixbuf_new_from_file(path);
    if (pixbuf != NULL) {
    global_buddyicon = gdk_pixbuf_scale_simple(pixbuf, 22, 22, GDK_INTERP_HYPER);
    g_object_unref(G_OBJECT(pixbuf));
    --- a/pidgin/gtkblist.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/pidgin/gtkblist.c Sun Aug 07 05:19:54 2011 +0000
    @@ -2643,7 +2643,6 @@
    gboolean scaled, gboolean greyed)
    {
    gsize len;
    - GdkPixbufLoader *loader;
    PurpleBuddy *buddy = NULL;
    PurpleGroup *group = NULL;
    const guchar *data = NULL;
    @@ -2710,21 +2709,20 @@
    return NULL;
    }
    - loader = gdk_pixbuf_loader_new();
    - gdk_pixbuf_loader_write(loader, data, len, NULL);
    - gdk_pixbuf_loader_close(loader, NULL);
    -
    - purple_imgstore_unref(custom_img);
    + buf = pidgin_pixbuf_from_data(data, len);
    purple_buddy_icon_unref(icon);
    -
    - buf = gdk_pixbuf_loader_get_pixbuf(loader);
    - if (buf)
    - g_object_ref(G_OBJECT(buf));
    - g_object_unref(G_OBJECT(loader));
    -
    if (!buf) {
    + purple_debug_warning("gtkblist", "Couldn't load buddy icon "
    + "on account %s (%s) buddyname=%s "
    + "custom_img_data=%p\n",
    + account ? purple_account_get_username(account) : "(no account)",
    + account ? purple_account_get_protocol_id(account) : "(no account)",
    + buddy ? purple_buddy_get_name(buddy) : "(no buddy)",
    + custom_img ? purple_imgstore_get_data(custom_img) : NULL);
    + purple_imgstore_unref(custom_img);
    return NULL;
    }
    + purple_imgstore_unref(custom_img);
    if (greyed) {
    gboolean offline = FALSE, idle = FALSE;
    @@ -3649,6 +3647,7 @@
    { N_("/Help/_Build Information"), NULL, pidgin_dialogs_buildinfo, 0, "<Item>", NULL },
    { N_("/Help/_Debug Window"), NULL, toggle_debug, 0, "<Item>", NULL },
    { N_("/Help/De_veloper Information"), NULL, pidgin_dialogs_developers, 0, "<Item>", NULL },
    + { N_("/Help/_Plugin Information"), NULL, pidgin_dialogs_plugins_info, 0, "<Item>", NULL },
    { N_("/Help/_Translator Information"), NULL, pidgin_dialogs_translators, 0, "<Item>", NULL },
    { "/Help/sep2", NULL, NULL, 0, "<Separator>", NULL },
    { N_("/Help/_About"), NULL, pidgin_dialogs_about, 4, "<StockItem>", GTK_STOCK_ABOUT },
    @@ -3952,7 +3951,7 @@
    g_object_ref(pb);
    g_free(path);
    } else {
    - pb = gdk_pixbuf_new_from_file(path, NULL);
    + pb = pidgin_pixbuf_new_from_file(path);
    if (pb != NULL) {
    /* We don't want to own a ref to the pixbuf, but we need to keep clean up. */
    /* I'm not sure if it would be better to just keep our ref and not let the emblem ever be destroyed */
    --- a/pidgin/gtkconv.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/pidgin/gtkconv.c Sun Aug 07 05:19:54 2011 +0000
    @@ -3978,6 +3978,16 @@
    }
    static void
    +deleting_chat_buddy_cb(PurpleConvChatBuddy *cb)
    +{
    + if (cb->ui_data) {
    + GtkTreeRowReference *ref = cb->ui_data;
    + gtk_tree_row_reference_free(ref);
    + cb->ui_data = NULL;
    + }
    +}
    +
    +static void
    add_chat_buddy_common(PurpleConversation *conv, PurpleConvChatBuddy *cb, const char *old_name)
    {
    PidginConversation *gtkconv;
    @@ -3985,18 +3995,20 @@
    PurpleConvChat *chat;
    PurpleConnection *gc;
    PurplePluginProtocolInfo *prpl_info;
    + GtkTreeModel *tm;
    GtkListStore *ls;
    + GtkTreePath *newpath;
    const char *stock;
    GtkTreeIter iter;
    gboolean is_me = FALSE;
    gboolean is_buddy;
    gchar *tmp, *alias_key, *name, *alias;
    - int flags;
    + PurpleConvChatBuddyFlags flags;
    GdkColor *color = NULL;
    alias = cb->alias;
    name = cb->name;
    - flags = GPOINTER_TO_INT(cb->flags);
    + flags = cb->flags;
    chat = PURPLE_CONV_CHAT(conv);
    gtkconv = PIDGIN_CONVERSATION(conv);
    @@ -4006,7 +4018,8 @@
    if (!gc || !(prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)))
    return;
    - ls = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat->list)));
    + tm = gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat->list));
    + ls = GTK_LIST_STORE(tm);
    stock = get_chat_buddy_status_icon(chat, name, flags);
    @@ -4051,6 +4064,15 @@
    CHAT_USERS_WEIGHT_COLUMN, is_buddy ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL,
    -1);
    + if (cb->ui_data) {
    + GtkTreeRowReference *ref = cb->ui_data;
    + gtk_tree_row_reference_free(ref);
    + }
    +
    + newpath = gtk_tree_model_get_path(tm, &iter);
    + cb->ui_data = gtk_tree_row_reference_new(tm, newpath);
    + gtk_tree_path_free(newpath);
    +
    if (is_me && color)
    gdk_color_free(color);
    g_free(alias_key);
    @@ -4346,6 +4368,12 @@
    CHAT_USERS_WEIGHT_COLUMN, &buddy2,
    -1);
    + /* Only sort by membership levels */
    + f1 &= PURPLE_CBFLAGS_VOICE | PURPLE_CBFLAGS_HALFOP | PURPLE_CBFLAGS_OP |
    + PURPLE_CBFLAGS_FOUNDER;
    + f2 &= PURPLE_CBFLAGS_VOICE | PURPLE_CBFLAGS_HALFOP | PURPLE_CBFLAGS_OP |
    + PURPLE_CBFLAGS_FOUNDER;
    +
    if (user1 == NULL || user2 == NULL) {
    if (!(user1 == NULL && user2 == NULL))
    ret = (user1 == NULL) ? -1: 1;
    @@ -6102,6 +6130,28 @@
    update_typing_message(gtkconv, NULL);
    }
    +static gboolean get_iter_from_chatbuddy(PurpleConvChatBuddy *cb, GtkTreeIter *iter)
    +{
    + GtkTreeRowReference *ref = cb->ui_data;
    + GtkTreePath *path;
    + GtkTreeModel *model;
    +
    + if (!ref)
    + return FALSE;
    +
    + if ((path = gtk_tree_row_reference_get_path(ref)) == NULL)
    + return FALSE;
    +
    + model = gtk_tree_row_reference_get_model(ref);
    + if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(model), iter, path)) {
    + gtk_tree_path_free(path);
    + return FALSE;
    + }
    +
    + gtk_tree_path_free(path);
    + return TRUE;
    +}
    +
    static void
    pidgin_conv_chat_add_users(PurpleConversation *conv, GList *cbuddies, gboolean new_arrivals)
    {
    @@ -6152,12 +6202,10 @@
    PurpleConvChat *chat;
    PidginConversation *gtkconv;
    PidginChatPane *gtkchat;
    - PurpleConvChatBuddyFlags flags;
    - PurpleConvChatBuddy *cbuddy;
    + PurpleConvChatBuddy *old_cbuddy, *new_cbuddy;
    GtkTreeIter iter;
    GtkTreeModel *model;
    GtkTextTag *tag;
    - int f = 1;
    chat = PURPLE_CONV_CHAT(conv);
    gtkconv = PIDGIN_CONVERSATION(conv);
    @@ -6168,20 +6216,13 @@
    if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &iter))
    return;
    - while (f != 0) {
    - char *val;
    -
    - gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, CHAT_USERS_NAME_COLUMN, &val, CHAT_USERS_FLAGS_COLUMN, &flags, -1);
    -
    - if (!purple_utf8_strcasecmp(old_name, val)) {
    - gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
    - g_free(val);
    - break;
    - }
    -
    - f = gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &iter);
    -
    - g_free(val);
    + old_cbuddy = purple_conv_chat_cb_find(chat, old_name);
    + if (get_iter_from_chatbuddy(old_cbuddy, &iter)) {
    + GtkTreeRowReference *ref = old_cbuddy->ui_data;
    +
    + gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
    + gtk_tree_row_reference_free(ref);
    + old_cbuddy->ui_data = NULL;
    }
    if ((tag = get_buddy_tag(conv, old_name, 0, FALSE)))
    @@ -6189,14 +6230,14 @@
    if ((tag = get_buddy_tag(conv, old_name, PURPLE_MESSAGE_NICK, FALSE)))
    g_object_set(G_OBJECT(tag), "style", PANGO_STYLE_ITALIC, NULL);
    - if (!purple_conv_chat_find_user(chat, old_name))
    + if (!old_cbuddy)
    return;
    g_return_if_fail(new_alias != NULL);
    - cbuddy = purple_conv_chat_cb_find(chat, new_name);
    -
    - add_chat_buddy_common(conv, cbuddy, old_name);
    + new_cbuddy = purple_conv_chat_cb_find(chat, new_name);
    +
    + add_chat_buddy_common(conv, new_cbuddy, old_name);
    }
    static void
    @@ -6223,6 +6264,7 @@
    model = gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat->list));
    if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &iter))
    + /* XXX: Break? */
    continue;
    do {
    @@ -6262,8 +6304,6 @@
    PidginChatPane *gtkchat;
    GtkTreeIter iter;
    GtkTreeModel *model;
    - int f = 1;
    - char *alias = NULL;
    chat = PURPLE_CONV_CHAT(conv);
    gtkconv = PIDGIN_CONVERSATION(conv);
    @@ -6274,35 +6314,16 @@
    if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &iter))
    return;
    - while (f != 0) {
    - char *val;
    -
    - gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, CHAT_USERS_NAME_COLUMN, &val, -1);
    -
    - if (!purple_utf8_strcasecmp(user, val)) {
    - gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, CHAT_USERS_ALIAS_COLUMN, &alias, -1);
    - gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
    - g_free(val);
    - break;
    - }
    -
    - f = gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &iter);
    -
    - g_free(val);
    - }
    -
    - if (!purple_conv_chat_find_user(chat, user))
    - {
    - g_free(alias);
    - return;
    - }
    -
    - g_return_if_fail(alias != NULL);
    -
    cbuddy = purple_conv_chat_cb_find(chat, user);
    + if (get_iter_from_chatbuddy(cbuddy, &iter)) {
    + GtkTreeRowReference *ref = cbuddy->ui_data;
    + gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
    + gtk_tree_row_reference_free(ref);
    + cbuddy->ui_data = NULL;
    + }
    +
    if (cbuddy)
    add_chat_buddy_common(conv, cbuddy, NULL);
    - g_free(alias);
    }
    gboolean
    @@ -6387,8 +6408,8 @@
    {
    PidginConversation *gtkconv;
    GtkIMHtmlSmiley *smiley;
    - GdkPixbufLoader *loader;
    const char *sml;
    + GError *error = NULL;
    sml = purple_account_get_protocol_name(conv->account);
    gtkconv = PIDGIN_CONVERSATION(conv);
    @@ -6401,11 +6422,24 @@
    g_memmove((guchar *)smiley->data + smiley->datasize, data, size);
    smiley->datasize += size;
    - loader = smiley->loader;
    - if (!loader)
    + if (!smiley->loader)
    return;
    - gdk_pixbuf_loader_write(loader, data, size, NULL);
    + if (!gdk_pixbuf_loader_write(smiley->loader, data, size, &error) || error) {
    + purple_debug_warning("gtkconv", "gdk_pixbuf_loader_write() "
    + "failed with size=%zu: %s\n", size,
    + error ? error->message : "(no error message)");
    + if (error)
    + g_error_free(error);
    + /* We must stop using the GdkPixbufLoader because trying to load
    + certain invalid GIFs with at least gdk-pixbuf 2.23.3 can return
    + a GdkPixbuf that will cause some operations (like
    + gdk_pixbuf_scale_simple()) to consume memory in an infinite loop.
    + But we also don't want to set smiley->loader to NULL because our
    + code might expect it to be set. So create a new loader. */
    + g_object_unref(G_OBJECT(smiley->loader));
    + smiley->loader = gdk_pixbuf_loader_new();
    + }
    }
    static void
    @@ -6413,8 +6447,8 @@
    {
    PidginConversation *gtkconv;
    GtkIMHtmlSmiley *smiley;
    - GdkPixbufLoader *loader;
    const char *sml;
    + GError *error = NULL;
    g_return_if_fail(conv != NULL);
    g_return_if_fail(smile != NULL);
    @@ -6426,17 +6460,27 @@
    if (!smiley)
    return;
    - loader = smiley->loader;
    -
    - if (!loader)
    + if (!smiley->loader)
    return;
    -
    -
    purple_debug_info("gtkconv", "About to close the smiley pixbuf\n");
    - gdk_pixbuf_loader_close(loader, NULL);
    -
    + if (!gdk_pixbuf_loader_close(smiley->loader, &error) || error) {
    + purple_debug_warning("gtkconv", "gdk_pixbuf_loader_close() "
    + "failed: %s\n",
    + error ? error->message : "(no error message)");
    + if (error)
    + g_error_free(error);
    + /* We must stop using the GdkPixbufLoader because if we tried to
    + load certain invalid GIFs with all current versions of GDK (as
    + of 2011-06-15) then it's possible the loader will contain data
    + that could cause some operations (like gdk_pixbuf_scale_simple())
    + to consume memory in an infinite loop. But we also don't want
    + to set smiley->loader to NULL because our code might expect it
    + to be set. So create a new loader. */
    + g_object_unref(G_OBJECT(smiley->loader));
    + smiley->loader = gdk_pixbuf_loader_new();
    + }
    }
    static void
    @@ -6589,7 +6633,7 @@
    if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM)
    {
    - gtk_widget_set_sensitive(win->menu.add, (prpl_info->add_buddy != NULL));
    + gtk_widget_set_sensitive(win->menu.add, (prpl_info->add_buddy != NULL) || (prpl_info->add_buddy_with_invite != NULL));
    gtk_widget_set_sensitive(win->menu.remove, (prpl_info->remove_buddy != NULL));
    gtk_widget_set_sensitive(win->menu.send_file,
    (prpl_info->send_file != NULL && (!prpl_info->can_receive_file ||
    @@ -6936,10 +6980,6 @@
    PurpleBuddy *buddy;
    - GdkPixbufLoader *loader;
    - GdkPixbufAnimation *anim;
    - GError *err = NULL;
    -
    PurpleStoredImage *custom_img = NULL;
    gconstpointer data = NULL;
    size_t len;
    @@ -7017,7 +7057,6 @@
    if (data == NULL) {
    icon = purple_conv_im_get_icon(PURPLE_CONV_IM(conv));
    -
    if (icon == NULL)
    {
    gtk_widget_set_size_request(gtkconv->u.im->icon_container,
    @@ -7026,7 +7065,6 @@
    }
    data = purple_buddy_icon_get_data(icon, &len);
    -
    if (data == NULL)
    {
    gtk_widget_set_size_request(gtkconv->u.im->icon_container,
    @@ -7035,25 +7073,13 @@
    }
    }
    - loader = gdk_pixbuf_loader_new();
    - gdk_pixbuf_loader_write(loader, data, len, NULL);
    - gdk_pixbuf_loader_close(loader, &err);
    -
    + gtkconv->u.im->anim = pidgin_pixbuf_anim_from_data(data, len);
    purple_imgstore_unref(custom_img);
    - anim = gdk_pixbuf_loader_get_animation(loader);
    - if (anim)
    - g_object_ref(G_OBJECT(anim));
    - g_object_unref(loader);
    -
    - if (!anim)
    + if (!gtkconv->u.im->anim) {
    + purple_debug_error("gtkconv", "Couldn't load icon for conv %s\n",
    + purple_conversation_get_name(conv));
    return;
    - gtkconv->u.im->anim = anim;
    -
    - if (err) {
    - purple_debug(PURPLE_DEBUG_ERROR, "gtkconv",
    - "Buddy icon error: %s\n", err->message);
    - g_error_free(err);
    }
    if (gdk_pixbuf_animation_is_static_image(gtkconv->u.im->anim)) {
    @@ -7857,6 +7883,8 @@
    purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/conversations/send_underline", FALSE);
    purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/conversations/spellcheck", TRUE);
    purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/conversations/show_incoming_formatting", TRUE);
    + purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/conversations/resize_custom_smileys", TRUE);
    + purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/custom_smileys_size", 96);
    purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/minimum_entry_lines", 2);
    purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/conversations/show_timestamps", TRUE);
    @@ -8083,6 +8111,9 @@
    purple_signal_connect(purple_conversations_get_handle(), "cleared-message-history",
    handle, G_CALLBACK(clear_conversation_scrollback_cb), NULL);
    + purple_signal_connect(purple_conversations_get_handle(), "deleting-chat-buddy",
    + handle, G_CALLBACK(deleting_chat_buddy_cb), NULL);
    +
    purple_conversations_set_ui_ops(&conversation_ui_ops);
    hidden_convwin = pidgin_conv_window_new();
    --- a/pidgin/gtkdialogs.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/pidgin/gtkdialogs.c Sun Aug 07 05:19:54 2011 +0000
    @@ -31,6 +31,7 @@
    #include "debug.h"
    #include "notify.h"
    +#include "plugin.h"
    #include "prpl.h"
    #include "request.h"
    #include "util.h"
    @@ -433,7 +434,7 @@
    /* Generate a logo with a version number */
    filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "logo.png", NULL);
    - pixbuf = gdk_pixbuf_new_from_file(filename, NULL);
    + pixbuf = pidgin_pixbuf_new_from_file(filename);
    g_free(filename);
    #if 0 /* Don't versionize the logo when the logo has the version in it */
    @@ -787,6 +788,52 @@
    g_free(tmp);
    }
    +void pidgin_dialogs_plugins_info(void)
    +{
    + GString *str;
    + GList *l = NULL;
    + PurplePlugin *plugin = NULL;
    + char *title = g_strdup_printf(_("%s Plugin Information"), PIDGIN_NAME);
    + char *pname = NULL, *pauthor = NULL;
    + const char *pver, *pwebsite, *pid;
    + gboolean ploaded, punloadable;
    + static GtkWidget *plugins_info = NULL;
    +
    + str = g_string_sized_new(4096);
    +
    + g_string_append_printf(str, "<FONT SIZE=\"4\">%s</FONT><BR/>",
    + _("Plugin Information"));
    +
    + for(l = purple_plugins_get_all(); l; l = l->next) {
    + plugin = (PurplePlugin *)l->data;
    +
    + pname = g_markup_escape_text(purple_plugin_get_name(plugin), -1);
    + pauthor = g_markup_escape_text(purple_plugin_get_author(plugin), -1);
    + pver = purple_plugin_get_version(plugin);
    + pwebsite = purple_plugin_get_homepage(plugin);
    + pid = purple_plugin_get_id(plugin);
    + punloadable = purple_plugin_is_unloadable(plugin);
    + ploaded = purple_plugin_is_loaded(plugin);
    +
    + g_string_append_printf(str,
    + "<FONT SIZE=\"3\"><B>%s</B></FONT><BR/><FONT SIZE=\"2\">"
    + "\t<B>Author:</B> %s<BR/>\t<B>Version:</B> %s<BR/>"
    + "\t<B>Website:</B> %s<BR/>\t<B>ID String:</B> %s<BR/>"
    + "\t<B>Loadable:</B> %s<BR/>\t<B>Loaded:</B> %s<BR/>"
    + "<BR/></FONT>", pname, pauthor ? pauthor : "(null)",
    + pver, pwebsite, pid,
    + punloadable ? "<FONT COLOR=\"#FF0000\"><B>No</B></FONT>" : "Yes",
    + ploaded ? "Yes" : "No");
    + }
    +
    + plugins_info = pidgin_build_help_dialog(title, "plugins_info", str);
    + g_signal_connect(G_OBJECT(plugins_info), "destroy",
    + G_CALLBACK(gtk_widget_destroyed), &plugins_info);
    + g_free(title);
    + g_free(pname);
    + g_free(pauthor);
    +}
    +
    static void
    pidgin_dialogs_im_cb(gpointer data, PurpleRequestFields *fields)
    {
    --- a/pidgin/gtkdialogs.h Sun Aug 07 03:21:39 2011 +0000
    +++ b/pidgin/gtkdialogs.h Sun Aug 07 05:19:54 2011 +0000
    @@ -36,6 +36,7 @@
    void pidgin_dialogs_buildinfo(void);
    void pidgin_dialogs_developers(void);
    void pidgin_dialogs_translators(void);
    +void pidgin_dialogs_plugins_info(void);
    void pidgin_dialogs_im(void);
    void pidgin_dialogs_im_with_user(PurpleAccount *, const char *);
    void pidgin_dialogs_info(void);
    --- a/pidgin/gtkft.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/pidgin/gtkft.c Sun Aug 07 05:19:54 2011 +0000
    @@ -1149,8 +1149,8 @@
    if (purple_xfer_get_size(xfer) <= PIDGIN_XFER_MAX_SIZE_IMAGE_THUMBNAIL) {
    GdkPixbuf *thumbnail =
    - gdk_pixbuf_new_from_file_at_size(
    - purple_xfer_get_local_filename(xfer), 128, 128, NULL);
    + pidgin_pixbuf_new_from_file_at_size(
    + purple_xfer_get_local_filename(xfer), 128, 128);
    if (thumbnail) {
    gchar **formats_split = g_strsplit(formats, ",", 0);
    --- a/pidgin/gtkimhtml.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/pidgin/gtkimhtml.c Sun Aug 07 05:19:54 2011 +0000
    @@ -5057,16 +5057,8 @@
    data = imhtml->funcs->image_get_data(image);
    len = imhtml->funcs->image_get_size(image);
    -
    - if (data && len) {
    - GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
    - gdk_pixbuf_loader_write(loader, data, len, NULL);
    - gdk_pixbuf_loader_close(loader, NULL);
    - anim = gdk_pixbuf_loader_get_animation(loader);
    - if (anim)
    - g_object_ref(G_OBJECT(anim));
    - g_object_unref(G_OBJECT(loader));
    - }
    + if (data && len)
    + anim = pidgin_pixbuf_anim_from_data(data, len);
    }
    @@ -5699,18 +5691,19 @@
    static void
    gtk_custom_smiley_size_prepared(GdkPixbufLoader *loader, gint width, gint height, gpointer data)
    {
    -#define CUSTOM_SMILEY_SIZE 96 /* XXX: Should this be a theme setting? */
    - if (width <= CUSTOM_SMILEY_SIZE && height <= CUSTOM_SMILEY_SIZE)
    - return;
    -
    - if (width >= height) {
    - height = height * CUSTOM_SMILEY_SIZE / width;
    - width = CUSTOM_SMILEY_SIZE;
    - } else {
    - width = width * CUSTOM_SMILEY_SIZE / height;
    - height = CUSTOM_SMILEY_SIZE;
    + if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/resize_custom_smileys")) {
    + int custom_smileys_size = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/custom_smileys_size");
    + if (width <= custom_smileys_size && height <= custom_smileys_size)
    + return;
    +
    + if (width >= height) {
    + height = height * custom_smileys_size / width;
    + width = custom_smileys_size;
    + } else {
    + width = width * custom_smileys_size / height;
    + height = custom_smileys_size;
    + }
    }
    -
    gdk_pixbuf_loader_set_size(loader, width, height);
    }
    --- a/pidgin/gtkmain.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/pidgin/gtkmain.c Sun Aug 07 05:19:54 2011 +0000
    @@ -270,7 +270,7 @@
    /* use the nice PNG icon for all the windows */
    for(i=0; i<G_N_ELEMENTS(icon_sizes); i++) {
    icon_path = g_build_filename(DATADIR, "icons", "hicolor", icon_sizes[i].dir, "apps", icon_sizes[i].filename, NULL);
    - icon = gdk_pixbuf_new_from_file(icon_path, NULL);
    + icon = pidgin_pixbuf_new_from_file(icon_path);
    g_free(icon_path);
    if (icon) {
    icons = g_list_append(icons,icon);
    @@ -381,8 +381,10 @@
    * account "markdoliner." Please don't use this key for other
    * applications. You can either not specify a client key, in
    * which case the default "libpurple" key will be used, or you
    - * can register for your own client key at
    - * http://developer.aim.com/manageKeys.jsp
    + * can try to register your own at the AIM or ICQ web sites
    + * (although this functionality was removed at some point, it's
    + * possible it has been re-added). AOL's old key management
    + * page is http://developer.aim.com/manageKeys.jsp
    */
    g_hash_table_insert(ui_info, "prpl-aim-clientkey", "ma1cSASNCKFtrdv9");
    g_hash_table_insert(ui_info, "prpl-icq-clientkey", "ma1cSASNCKFtrdv9");
    --- a/pidgin/gtkmedia.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/pidgin/gtkmedia.c Sun Aug 07 05:19:54 2011 +0000
    @@ -538,12 +538,20 @@
    GdkWindow *window = NULL;
    if (data->participant == NULL)
    +#if GTK_CHECK_VERSION(2, 14, 0)
    window = gtk_widget_get_window(priv->local_video);
    +#else
    + window = (priv->local_video)->window;
    +#endif
    else {
    GtkWidget *widget = pidgin_media_get_widget(data->gtkmedia,
    data->session_id, data->participant);
    if (widget)
    +#if GTK_CHECK_VERSION(2, 14, 0)
    window = gtk_widget_get_window(widget);
    +#else
    + window = widget->window;
    +#endif
    }
    if (window) {
    --- a/pidgin/gtkprefs.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/pidgin/gtkprefs.c Sun Aug 07 05:19:54 2011 +0000
    @@ -382,7 +382,7 @@
    * LEAK - Gentoo memprof thinks pixbuf is leaking here... but it
    * looks like it should be ok to me. Anyone know what's up? --Mark
    */
    - pixbuf = (theme->icon ? gdk_pixbuf_new_from_file(theme->icon, NULL) : NULL);
    + pixbuf = (theme->icon ? pidgin_pixbuf_new_from_file(theme->icon) : NULL);
    gtk_list_store_set(prefs_smiley_themes, &iter,
    0, pixbuf,
    @@ -452,7 +452,7 @@
    image_full = purple_theme_get_image_full(theme);
    if (image_full != NULL){
    - pixbuf = gdk_pixbuf_new_from_file_at_scale(image_full, PREFS_OPTIMAL_ICON_SIZE, PREFS_OPTIMAL_ICON_SIZE, TRUE, NULL);
    + pixbuf = pidgin_pixbuf_new_from_file_at_scale(image_full, PREFS_OPTIMAL_ICON_SIZE, PREFS_OPTIMAL_ICON_SIZE, TRUE);
    g_free(image_full);
    } else
    pixbuf = NULL;
    @@ -473,7 +473,7 @@
    image_full = purple_theme_get_image_full(theme);
    if (image_full != NULL){
    - pixbuf = gdk_pixbuf_new_from_file_at_scale(image_full, PREFS_OPTIMAL_ICON_SIZE, PREFS_OPTIMAL_ICON_SIZE, TRUE, NULL);
    + pixbuf = pidgin_pixbuf_new_from_file_at_scale(image_full, PREFS_OPTIMAL_ICON_SIZE, PREFS_OPTIMAL_ICON_SIZE, TRUE);
    g_free(image_full);
    } else
    pixbuf = NULL;
    @@ -529,7 +529,7 @@
    purple_theme_manager_refresh();
    tmp = g_build_filename(DATADIR, "icons", "hicolor", "32x32", "apps", "pidgin.png", NULL);
    - pixbuf = gdk_pixbuf_new_from_file_at_scale(tmp, PREFS_OPTIMAL_ICON_SIZE, PREFS_OPTIMAL_ICON_SIZE, TRUE, NULL);
    + pixbuf = pidgin_pixbuf_new_from_file_at_scale(tmp, PREFS_OPTIMAL_ICON_SIZE, PREFS_OPTIMAL_ICON_SIZE, TRUE);
    g_free(tmp);
    /* sound themes */
    @@ -1448,6 +1448,9 @@
    GtkWidget *iconpref2;
    GtkWidget *imhtml;
    GtkWidget *frame;
    + GtkWidget *hbox;
    + GtkWidget *checkbox;
    + GtkWidget *spin_button;
    ret = gtk_vbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
    gtk_container_set_border_width(GTK_CONTAINER(ret), PIDGIN_HIG_BORDER);
    @@ -1480,6 +1483,24 @@
    #ifdef _WIN32
    pidgin_prefs_checkbox(_("F_lash window when IMs are received"), PIDGIN_PREFS_ROOT "/win32/blink_im", vbox);
    #endif
    + hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
    +
    + checkbox = pidgin_prefs_checkbox(_("Resize incoming custom smileys"),
    + PIDGIN_PREFS_ROOT "/conversations/resize_custom_smileys", hbox);
    +
    + spin_button = pidgin_prefs_labeled_spin_button(hbox,
    + _("Maximum size:"),
    + PIDGIN_PREFS_ROOT "/conversations/custom_smileys_size",
    + 16, 512, NULL);
    +
    + if (!purple_prefs_get_bool(
    + PIDGIN_PREFS_ROOT "/conversations/resize_custom_smileys"))
    + gtk_widget_set_sensitive(GTK_WIDGET(spin_button), FALSE);
    +
    + g_signal_connect(G_OBJECT(checkbox), "clicked",
    + G_CALLBACK(pidgin_toggle_sensitive), spin_button);
    +
    + pidgin_add_widget_to_vbox(GTK_BOX(vbox), NULL, NULL, hbox, TRUE, NULL);
    pidgin_prefs_labeled_spin_button(vbox,
    _("Minimum input area height in lines:"),
    --- a/pidgin/gtkrequest.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/pidgin/gtkrequest.c Sun Aug 07 05:19:54 2011 +0000
    @@ -653,35 +653,30 @@
    /* Dialog icon. */
    if (icon_data) {
    - GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
    - GdkPixbuf *pixbuf = NULL;
    - if (gdk_pixbuf_loader_write(loader, icon_data, icon_size, NULL)) {
    - pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
    - if (pixbuf) {
    - /* scale the image if it is too large */
    - int width = gdk_pixbuf_get_width(pixbuf);
    - int height = gdk_pixbuf_get_height(pixbuf);
    - if (width > 128 || height > 128) {
    - int scaled_width = width > height ? 128 : (128 * width) / height;
    - int scaled_height = height > width ? 128 : (128 * height) / width;
    - GdkPixbuf *scaled =
    - gdk_pixbuf_scale_simple(pixbuf, scaled_width, scaled_height,
    - GDK_INTERP_BILINEAR);
    + GdkPixbuf *pixbuf = pidgin_pixbuf_from_data(icon_data, icon_size);
    + if (pixbuf) {
    + /* scale the image if it is too large */
    + int width = gdk_pixbuf_get_width(pixbuf);
    + int height = gdk_pixbuf_get_height(pixbuf);
    + if (width > 128 || height > 128) {
    + int scaled_width = width > height ? 128 : (128 * width) / height;
    + int scaled_height = height > width ? 128 : (128 * height) / width;
    + GdkPixbuf *scaled =
    + gdk_pixbuf_scale_simple(pixbuf, scaled_width, scaled_height,
    + GDK_INTERP_BILINEAR);
    - purple_debug_info("pidgin",
    - "dialog icon was too large, scale it down\n");
    - if (scaled) {
    - g_object_unref(pixbuf);
    - pixbuf = scaled;
    - }
    + purple_debug_info("pidgin",
    + "dialog icon was too large, scaled it down\n");
    + if (scaled) {
    + g_object_unref(pixbuf);
    + pixbuf = scaled;
    }
    - img = gtk_image_new_from_pixbuf(pixbuf);
    }
    + img = gtk_image_new_from_pixbuf(pixbuf);
    + g_object_unref(pixbuf);
    } else {
    purple_debug_info("pidgin", "failed to parse dialog icon\n");
    }
    - gdk_pixbuf_loader_close(loader, NULL);
    - g_object_unref(loader);
    }
    if (!img) {
    @@ -1040,22 +1035,17 @@
    {
    GtkWidget *widget;
    GdkPixbuf *buf, *scale;
    - GdkPixbufLoader *loader;
    - loader = gdk_pixbuf_loader_new();
    - gdk_pixbuf_loader_write(loader,
    - (const guchar *)purple_request_field_image_get_buffer(field),
    - purple_request_field_image_get_size(field),
    - NULL);
    - gdk_pixbuf_loader_close(loader, NULL);
    - buf = gdk_pixbuf_loader_get_pixbuf(loader);
    + buf = pidgin_pixbuf_from_data(
    + (const guchar *)purple_request_field_image_get_buffer(field),
    + purple_request_field_image_get_size(field));
    scale = gdk_pixbuf_scale_simple(buf,
    purple_request_field_image_get_scale_x(field) * gdk_pixbuf_get_width(buf),
    purple_request_field_image_get_scale_y(field) * gdk_pixbuf_get_height(buf),
    GDK_INTERP_BILINEAR);
    widget = gtk_image_new_from_pixbuf(scale);
    - g_object_unref(G_OBJECT(loader));
    + g_object_unref(G_OBJECT(buf));
    g_object_unref(G_OBJECT(scale));
    #if GTK_CHECK_VERSION(2,12,0)
    @@ -1164,7 +1154,7 @@
    GdkPixbuf* pixbuf = NULL;
    if (icon_path)
    - pixbuf = gdk_pixbuf_new_from_file(icon_path, NULL);
    + pixbuf = pidgin_pixbuf_new_from_file(icon_path);
    gtk_list_store_set(store, &iter,
    0, purple_request_field_list_get_data(field, text),
    --- a/pidgin/gtksmiley.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/pidgin/gtksmiley.c Sun Aug 07 05:19:54 2011 +0000
    @@ -332,7 +332,7 @@
    g_free(s->filename);
    s->filename = g_strdup(filename);
    - pixbuf = gdk_pixbuf_new_from_file_at_scale(filename, 64, 64, FALSE, NULL);
    + pixbuf = pidgin_pixbuf_new_from_file_at_scale(filename, 64, 64, FALSE);
    gtk_image_set_from_pixbuf(GTK_IMAGE(s->smiley_image), pixbuf);
    if (pixbuf)
    g_object_unref(G_OBJECT(pixbuf));
    @@ -690,7 +690,6 @@
    FILE *f;
    gchar *path;
    size_t wc;
    - GError *err = NULL;
    PidginSmiley *ps;
    GdkPixbuf *image;
    @@ -709,13 +708,11 @@
    }
    fclose(f);
    - image = gdk_pixbuf_new_from_file(path, &err);
    + image = pidgin_pixbuf_new_from_file(path);
    g_unlink(path);
    g_free(path);
    - if (err) {
    - g_error_free(err);
    + if (!image)
    return;
    - }
    ps = pidgin_smiley_edit(dialog->window, NULL);
    pidgin_smiley_editor_set_image(ps, image);
    --- a/pidgin/gtkstatusbox.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/pidgin/gtkstatusbox.c Sun Aug 07 05:19:54 2011 +0000
    @@ -2225,22 +2225,45 @@
    if (status_box->buddy_icon_img != NULL)
    {
    - GdkPixbuf *buf, *scale;
    - int scale_width, scale_height;
    - GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
    + GdkPixbufLoader *loader;
    + GError *error = NULL;
    +
    + loader = gdk_pixbuf_loader_new();
    +
    g_signal_connect(G_OBJECT(loader), "size-prepared", G_CALLBACK(pixbuf_size_prepared_cb), NULL);
    - gdk_pixbuf_loader_write(loader, purple_imgstore_get_data(status_box->buddy_icon_img),
    - purple_imgstore_get_size(status_box->buddy_icon_img), NULL);
    - gdk_pixbuf_loader_close(loader, NULL);
    - buf = gdk_pixbuf_loader_get_pixbuf(loader);
    - scale_width = gdk_pixbuf_get_width(buf);
    - scale_height = gdk_pixbuf_get_height(buf);
    - scale = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, scale_width, scale_height);
    - gdk_pixbuf_fill(scale, 0x00000000);
    - gdk_pixbuf_copy_area(buf, 0, 0, scale_width, scale_height, scale, 0, 0);
    - if (pidgin_gdk_pixbuf_is_opaque(scale))
    - pidgin_gdk_pixbuf_make_round(scale);
    - status_box->buddy_icon = scale;
    + if (!gdk_pixbuf_loader_write(loader,
    + purple_imgstore_get_data(status_box->buddy_icon_img),
    + purple_imgstore_get_size(status_box->buddy_icon_img),
    + &error) || error)
    + {
    + purple_debug_warning("gtkstatusbox", "gdk_pixbuf_loader_write() "
    + "failed with size=%zu: %s\n",
    + purple_imgstore_get_size(status_box->buddy_icon_img),
    + error ? error->message : "(no error message)");
    + if (error)
    + g_error_free(error);
    + } else if (!gdk_pixbuf_loader_close(loader, &error) || error) {
    + purple_debug_warning("gtkstatusbox", "gdk_pixbuf_loader_close() "
    + "failed for image of size %zu: %s\n",
    + purple_imgstore_get_size(status_box->buddy_icon_img),
    + error ? error->message : "(no error message)");
    + if (error)
    + g_error_free(error);
    + } else {
    + GdkPixbuf *buf, *scale;
    + int scale_width, scale_height;
    +
    + buf = gdk_pixbuf_loader_get_pixbuf(loader);
    + scale_width = gdk_pixbuf_get_width(buf);
    + scale_height = gdk_pixbuf_get_height(buf);
    + scale = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, scale_width, scale_height);
    + gdk_pixbuf_fill(scale, 0x00000000);
    + gdk_pixbuf_copy_area(buf, 0, 0, scale_width, scale_height, scale, 0, 0);
    + if (pidgin_gdk_pixbuf_is_opaque(scale))
    + pidgin_gdk_pixbuf_make_round(scale);
    + status_box->buddy_icon = scale;
    + }
    +
    g_object_unref(loader);
    }
    --- a/pidgin/gtkutils.c Sun Aug 07 03:21:39 2011 +0000
    +++ b/pidgin/gtkutils.c Sun Aug 07 05:19:54 2011 +0000
    @@ -615,7 +615,7 @@
    tmp, NULL);
    g_free(tmp);
    - pixbuf = gdk_pixbuf_new_from_file(filename, NULL);
    + pixbuf = pidgin_pixbuf_new_from_file(filename);
    g_free(filename);
    return pixbuf;
    @@ -704,7 +704,7 @@
    "16", "google-talk.png", NULL);
    GtkWidget *item;
    - pixbuf = gdk_pixbuf_new_from_file(filename, NULL);
    + pixbuf = pidgin_pixbuf_new_from_file(filename);
    g_free(filename);
    gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu->menu),
    @@ -723,7 +723,7 @@
    "16", "facebook.png", NULL);
    GtkWidget *item;
    - pixbuf = gdk_pixbuf_new_from_file(filename, NULL);
    + pixbuf = pidgin_pixbuf_new_from_file(filename);
    g_free(filename);
    gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu->menu),
    @@ -1593,7 +1593,7 @@
    }
    /* Are we dealing with an image? */
    - pb = gdk_pixbuf_new_from_file(filename, NULL);
    + pb = pidgin_pixbuf_new_from_file(filename);
    if (pb) {
    _DndData *data = g_malloc(sizeof(_DndData));
    gboolean ft = FALSE, im = FALSE;
    @@ -2265,7 +2265,7 @@
    filename = gtk_file_chooser_get_preview_filename(
    GTK_FILE_CHOOSER(dialog->icon_filesel));
    - if (!filename || g_stat(filename, &st) || !(pixbuf = gdk_pixbuf_new_from_file(filename, NULL)))
    + if (!filename || g_stat(filename, &st) || !(pixbuf = pidgin_pixbuf_new_from_file(filename)))
    {
    gtk_image_set_from_pixbuf(GTK_IMAGE(dialog->icon_preview), NULL);
    gtk_label_set_markup(GTK_LABEL(dialog->icon_text), "");
    @@ -3086,17 +3086,134 @@
    #endif
    }
    -GdkPixbuf * pidgin_pixbuf_from_imgstore(PurpleStoredImage *image)
    +static GObject *pidgin_pixbuf_from_data_helper(const guchar *buf, gsize count, gboolean animated)
    +{
    + GObject *pixbuf;
    + GdkPixbufLoader *loader;
    + GError *error = NULL;
    +
    + loader = gdk_pixbuf_loader_new();
    +
    + if (!gdk_pixbuf_loader_write(loader, buf, count, &error) || error) {
    + purple_debug_warning("gtkutils", "gdk_pixbuf_loader_write() "
    + "failed with size=%zu: %s\n", count,
    + error ? error->message : "(no error message)");
    + if (error)
    + g_error_free(error);
    + g_object_unref(G_OBJECT(loader));
    + return NULL;
    + }
    +
    + if (!gdk_pixbuf_loader_close(loader, &error) || error) {
    + purple_debug_warning("gtkutils", "gdk_pixbuf_loader_close() "
    + "failed for image of size %zu: %s\n", count,
    + error ? error->message : "(no error message)");
    + if (error)
    + g_error_free(error);
    + g_object_unref(G_OBJECT(loader));
    + return NULL;
    + }
    +
    + if (animated)
    + pixbuf = G_OBJECT(gdk_pixbuf_loader_get_animation(loader));
    + else
    + pixbuf = G_OBJECT(gdk_pixbuf_loader_get_pixbuf(loader));
    + if (!pixbuf) {
    + purple_debug_warning("gtkutils", "%s() returned NULL for image "
    + "of size %zu\n",
    + animated ? "gdk_pixbuf_loader_get_animation"
    + : "gdk_pixbuf_loader_get_pixbuf", count);
    + g_object_unref(G_OBJECT(loader));
    + return NULL;
    + }
    +
    + g_object_ref(pixbuf);
    + g_object_unref(G_OBJECT(loader));
    +
    + return pixbuf;
    +}
    +
    +GdkPixbuf *pidgin_pixbuf_from_data(const guchar *buf, gsize count)
    +{
    + return GDK_PIXBUF(pidgin_pixbuf_from_data_helper(buf, count, FALSE));
    +}
    +
    +GdkPixbufAnimation *pidgin_pixbuf_anim_from_data(const guchar *buf, gsize count)
    +{
    + return GDK_PIXBUF_ANIMATION(pidgin_pixbuf_from_data_helper(buf, count, TRUE));
    +}
    +
    +GdkPixbuf *pidgin_pixbuf_from_imgstore(PurpleStoredImage *image)
    +{
    + return pidgin_pixbuf_from_data(purple_imgstore_get_data(image),
    + purple_imgstore_get_size(image));
    +}
    +
    +GdkPixbuf *pidgin_pixbuf_new_from_file(const gchar *filename)
    {
    GdkPixbuf *pixbuf;
    - GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
    - gdk_pixbuf_loader_write(loader, purple_imgstore_get_data(image),
    - purple_imgstore_get_size(image), NULL);
    - gdk_pixbuf_loader_close(loader, NULL);
    - pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
    - if (pixbuf)
    - g_object_ref(pixbuf);
    - g_object_unref(loader);
    + GError *error = NULL;
    +
    + pixbuf = gdk_pixbuf_new_from_file(filename, &error);
    + if (!pixbuf || error) {
    + purple_debug_warning("gtkutils", "gdk_pixbuf_new_from_file() "
    + "returned %s for file %s: %s\n",
    + pixbuf ? "something" : "nothing",
    + filename,
    + error ? error->message : "(no error message)");
    + if (error)
    + g_error_free(error);
    + if (pixbuf)
    + g_object_unref(G_OBJECT(pixbuf));
    + return NULL;
    + }
    +
    + return pixbuf;
    +}
    +
    +GdkPixbuf *pidgin_pixbuf_new_from_file_at_size(const char *filename, int width, int height)
    +{
    + GdkPixbuf *pixbuf;
    + GError *error = NULL;
    +
    + pixbuf = gdk_pixbuf_new_from_file_at_size(filename,
    + width, height, &error);
    + if (!pixbuf || error) {
    + purple_debug_warning("gtkutils", "gdk_pixbuf_new_from_file_at_size() "
    + "returned %s for file %s: %s\n",
    + pixbuf ? "something" : "nothing",
    + filename,
    + error ? error->message : "(no error message)");
    + if (error)
    + g_error_free(error);
    + if (pixbuf)
    + g_object_unref(G_OBJECT(pixbuf));
    + return NULL;
    + }
    +
    + return pixbuf;
    +}
    +
    +GdkPixbuf *pidgin_pixbuf_new_from_file_at_scale(const char *filename, int width, int height, gboolean preserve_aspect_ratio)
    +{
    + GdkPixbuf *pixbuf;
    + GError *error = NULL;
    +
    + pixbuf = gdk_pixbuf_new_from_file_at_scale(filename,
    + width, height, preserve_aspect_ratio, &error);
    + if (!pixbuf || error) {
    + purple_debug_warning("gtkutils", "gdk_pixbuf_new_from_file_at_scale() "
    + "returned %s for file %s: %s\n",
    + pixbuf ? "something" : "nothing",
    + filename,
    + error ? error->message : "(no error message)");
    + if (error)
    + g_error_free(error);
    + if (pixbuf)
    + g_object_unref(G_OBJECT(pixbuf));
    + return NULL;
    + }
    +
    return pixbuf;
    }
    --- a/pidgin/gtkutils.h Sun Aug 07 03:21:39 2011 +0000
    +++ b/pidgin/gtkutils.h Sun Aug 07 05:19:54 2011 +0000
    @@ -834,6 +834,32 @@
    GtkWidget *pidgin_add_widget_to_vbox(GtkBox *vbox, const char *widget_label, GtkSizeGroup *sg, GtkWidget *widget, gboolean expand, GtkWidget **p_label);
    /**
    + * Create a GdkPixbuf from a chunk of image data.
    + *
    + * @param buf The raw binary image data.
    + * @param count The length of buf in bytes.
    + *
    + * @return A GdkPixbuf created from the image data, or NULL if
    + * there was an error parsing the data.
    + *
    + * @since 2.9.0
    + */
    +GdkPixbuf *pidgin_pixbuf_from_data(const guchar *buf, gsize count);
    +
    +/**
    + * Create a GdkPixbufAnimation from a chunk of image data.
    + *
    + * @param buf The raw binary image data.
    + * @param count The length of buf in bytes.
    + *
    + * @return A GdkPixbufAnimation created from the image data, or NULL if
    + * there was an error parsing the data.
    + *
    + * @since 2.9.0
    + */
    +GdkPixbufAnimation *pidgin_pixbuf_anim_from_data(const guchar *buf, gsize count);
    +
    +/**
    * Create a GdkPixbuf from a PurpleStoredImage.
    *
    * @param image A PurpleStoredImage.
    @@ -845,6 +871,86 @@
    GdkPixbuf *pidgin_pixbuf_from_imgstore(PurpleStoredImage *image);
    /**
    + * Helper function that calls gdk_pixbuf_new_from_file() and checks both
    + * the return code and the GError and returns NULL if either one failed.
    + *
    + * The gdk-pixbuf documentation implies that it is sufficient to check
    + * the return value of gdk_pixbuf_new_from_file() to determine
    + * whether the image was able to be loaded. However, this is not the case
    + * with gdk-pixbuf 2.23.3 and probably many earlier versions. In some
    + * cases a GdkPixbuf object is returned that will cause some operations
    + * (like gdk_pixbuf_scale_simple()) to rapidly consume memory in an
    + * infinite loop.
    + *
    + * This function shouldn't be necessary once Pidgin requires a version of
    + * gdk-pixbuf where the aforementioned bug is fixed. However, it might be
    + * nice to keep this function around for the debug message that it logs.
    + *
    + * @param filename Name of file to load, in the GLib file name encoding
    + *
    + * @return The GdkPixbuf if successful. Otherwise NULL is returned and
    + * a warning is logged.
    + *
    + * @since 2.9.0
    + */
    +GdkPixbuf *pidgin_pixbuf_new_from_file(const char *filename);
    +
    +/**
    + * Helper function that calls gdk_pixbuf_new_from_file_at_size() and checks
    + * both the return code and the GError and returns NULL if either one failed.
    + *
    + * The gdk-pixbuf documentation implies that it is sufficient to check
    + * the return value of gdk_pixbuf_new_from_file_at_size() to determine
    + * whether the image was able to be loaded. However, this is not the case
    + * with gdk-pixbuf 2.23.3 and probably many earlier versions. In some
    + * cases a GdkPixbuf object is returned that will cause some operations
    + * (like gdk_pixbuf_scale_simple()) to rapidly consume memory in an
    + * infinite loop.
    + *
    + * This function shouldn't be necessary once Pidgin requires a version of
    + * gdk-pixbuf where the aforementioned bug is fixed. However, it might be
    + * nice to keep this function around for the debug message that it logs.
    + *
    + * @param filename Name of file to load, in the GLib file name encoding
    + * @param width The width the image should have or -1 to not constrain the width
    + * @param height The height the image should have or -1 to not constrain the height
    + *
    + * @return The GdkPixbuf if successful. Otherwise NULL is returned and
    + * a warning is logged.
    + *
    + * @since 2.9.0
    + */
    +GdkPixbuf *pidgin_pixbuf_new_from_file_at_size(const char *filename, int width, int height);
    +
    +/**
    + * Helper function that calls gdk_pixbuf_new_from_file_at_scale() and checks
    + * both the return code and the GError and returns NULL if either one failed.
    + *
    + * The gdk-pixbuf documentation implies that it is sufficient to check
    + * the return value of gdk_pixbuf_new_from_file_at_scale() to determine
    + * whether the image was able to be loaded. However, this is not the case
    + * with gdk-pixbuf 2.23.3 and probably many earlier versions. In some
    + * cases a GdkPixbuf object is returned that will cause some operations
    + * (like gdk_pixbuf_scale_simple()) to rapidly consume memory in an
    + * infinite loop.
    + *
    + * This function shouldn't be necessary once Pidgin requires a version of
    + * gdk-pixbuf where the aforementioned bug is fixed. However, it might be
    + * nice to keep this function around for the debug message that it logs.
    + *
    + * @param filename Name of file to load, in the GLib file name encoding
    + * @param width The width the image should have or -1 to not constrain the width
    + * @param height The height the image should have or -1 to not constrain the height
    + * @param preserve_aspect_ratio TRUE to preserve the image's aspect ratio
    + *
    + * @return The GdkPixbuf if successful. Otherwise NULL is returned and
    + * a warning is logged.
    + *
    + * @since 2.9.0
    + */
    +GdkPixbuf *pidgin_pixbuf_new_from_file_at_scale(const char *filename, int width, int height, gboolean preserve_aspect_ratio);
    +
    +/**
    * Add scrollbars to a widget
    * @param widget The child widget
    * @hscrollbar_policy Horizontal scrolling policy
    --- a/po/ChangeLog Sun Aug 07 03:21:39 2011 +0000
    +++ b/po/ChangeLog Sun Aug 07 05:19:54 2011 +0000
    @@ -1,5 +1,11 @@
    Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
    +version 2.9.1
    + * German translation updated (Björn Voigt, Jochen Kemnade)
    +
    +version 2.9.0
    + * No changes
    +
    version 2.8.0
    * Albanian translation updated (Besnik Bleta)
    * Bengali translation updated (Jamil Ahmed)
    --- a/po/de.po Sun Aug 07 03:21:39 2011 +0000
    +++ b/po/de.po Sun Aug 07 05:19:54 2011 +0000
    @@ -11,8 +11,8 @@
    msgstr ""
    "Project-Id-Version: de\n"
    "Report-Msgid-Bugs-To: \n"
    -"POT-Creation-Date: 2011-05-12 09:37+0200\n"
    -"PO-Revision-Date: 2011-05-12 09:37+0200\n"
    +"POT-Creation-Date: 2011-07-31 12:34+0200\n"
    +"PO-Revision-Date: 2011-07-31 12:33+0200\n"
    "Last-Translator: Björn Voigt <bjoern@cs.tu-berlin.de>\n"
    "Language-Team: German <de@li.org>\n"
    "Language: de\n"
    @@ -4778,8 +4778,8 @@
    msgstr ""
    "invite &lt;Benutzer&gt; [Nachricht]: Lade einen Benutzer in den Raum ein."
    -msgid "join: &lt;room&gt; [password]: Join a chat on this server."
    -msgstr "join: &lt;Raum&gt; [Passwort]: Betrete einen Chat auf diesem Server."
    +msgid "join: &lt;room[@server]&gt; [password]: Join a chat."
    +msgstr "join: &lt;Raum[@Server]&gt; [Passwort]: Betrete einen Chat."
    msgid "kick &lt;user&gt; [reason]: Kick a user from the room."
    msgstr "kick &lt;Benutzer&gt; [Grund]: Kickt einen Benutzer aus dem Raum."
    @@ -6361,13 +6361,16 @@
    msgstr "Wo ich wohne"
    #, c-format
    -msgid "You have %i suggested friends."
    -msgstr "Sie haben %i vorgeschlagene(n) Freund(e)."
    -
    -#, c-format
    -msgid "We found %i contacts that match your search."
    -msgstr ""
    -"Wir fanden %i Kontakt(e), die Ihrer Suchanfrage entspricht/entsprechen."
    +msgid "You have %i suggested friend."
    +msgid_plural "You have %i suggested friends."
    +msgstr[0] "Sie haben %i vorgeschlagenen Freund."
    +msgstr[1] "Sie haben %i vorgeschlagene Freunde."
    +
    +#, c-format
    +msgid "We found %i contact that matches your search."
    +msgid_plural "We found %i contacts that match your search."
    +msgstr[0] "Wir fanden %i Kontakt, der Ihrer Suchanfrage entspricht."
    +msgstr[1] "Wir fanden %i Kontakte, die Ihrer Suchanfrage entsprechen."
    #. we must have lost the connection, so terminate it so that we can reconnect
    msgid "We have lost the connection to MXit. Please reconnect."
    @@ -11131,6 +11134,9 @@
    msgid "/Help/De_veloper Information"
    msgstr "/Hilfe/_Entwickler-Informationen"
    +msgid "/Help/_Plugin Information"
    +msgstr "/Hilfe/_Plugin-Informationen"
    +
    msgid "/Help/_Translator Information"
    msgstr "/Hilfe/Über_setzer-Informationen"
    @@ -12146,6 +12152,13 @@
    msgid "%s Translator Information"
    msgstr "%s-Übersetzer-Informationen"
    +#, c-format
    +msgid "%s Plugin Information"
    +msgstr "%s Plugin-Informationen"
    +
    +msgid "Plugin Information"
    +msgstr "Plugin-Informationen"
    +
    msgid "_Name"
    msgstr "_Name"
    @@ -13113,6 +13126,12 @@
    msgid "F_lash window when IMs are received"
    msgstr "Fenster b_linkt, wenn IM-Nachrichten empfangen werden"
    +msgid "Resize incoming custom smileys"
    +msgstr "Erhaltene benutzerdefinierte Smileys skalieren"
    +
    +msgid "Maximum size:"
    +msgstr "Maximale Größe:"
    +
    msgid "Minimum input area height in lines:"
    msgstr "Minimale Höhe des Eingabefeldes in Zeilen:"
    @@ -13182,6 +13201,9 @@
    msgid "_UDP Port:"
    msgstr "_UDP-Port:"
    +msgid "T_CP Port:"
    +msgstr "T_CP-Port:"
    +
    msgid "Use_rname:"
    msgstr "_Benutzername:"