Thu, 13 Aug 2009 17:15:06 +0000
propagate from branch 'im.pidgin.pidgin' (head 2c70cc3e4c687ded823027750c5230825eb56825)
to branch 'im.pidgin.cpw.malu.ft_thumbnails' (head 0fb38e6247229d447693b04c74068211d5d21682)
--- a/COPYRIGHT Tue Jul 28 20:51:56 2009 +0000 +++ b/COPYRIGHT Thu Aug 13 17:15:06 2009 +0000 @@ -1,8 +1,21 @@ Pidgin, Finch, and libpurple -Copyright (C) 1998-2009 by the following: + +This file is intended to be a comprehensive list of contributors to +this project. If you have contributed to this project then you deserve +to be on this list. Contact us (see: AUTHORS) and we'll add you. -If you have contributed to this project then you deserve to be on this -list. Contact us (see: AUTHORS) and we'll add you. +Many open source projects list contributor names at the top of each +source file containing their contribution. However, we've found +that it is difficult to keep this list accurate, especially when old +code is removed or existing code is moved to a different file. So +instead we chose to list a generic message at the top of each source +file that points here. + +If concerns are raised as to the copyright holder of a particular +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: Saleem Abdulrasool Dave Ahlswede
--- a/ChangeLog Tue Jul 28 20:51:56 2009 +0000 +++ b/ChangeLog Thu Aug 13 17:15:06 2009 +0000 @@ -43,6 +43,12 @@ against the GNU IDN library. * Install scalable versions of the main Pidgin icon, the protocol icons, the dialog icons, and the Buddy List emblems. + * Build properly on Hurd. (Marc Dequènes) + * Various memory leaks fixed as reported by Josh Mueller. + * Properly handle an IRC buddy appearing in multiple groups. + * Escape HTML entities in usernames when written with the HTML logger. + * Do not display MySpace status changes as incoming IMs. (Mark Doliner and + Justin Williams) AIM and ICQ: * Preliminary support for a new authentication scheme called @@ -113,6 +119,8 @@ * When the GNU IDN library (libidn) is available, it is used for normalization of Jabber IDs. When unavailable, internal routines are used (as in previous versions). + * Topics that contain '<' followed by a non-whitespace character can now + be set properly. Yahoo!/Yahoo! JAPAN: * P2P file transfers. (Sulabh Mahajan) @@ -173,6 +181,12 @@ conversation window is now linked to the file. * Fix a crash when closing a conversation tab that has unread messages when the Message Notification plugin is loaded. + * Fix a crash when closing the New Mail dialog if an account with new + mail was previously disconnected while the dialog was open. + * Fix incorrect unread message counts for the new mail notifications. + * Do not lose unread messages with a hidden conversation window when + new IM conversations are hidden and "Close IMs immediately when the tab + is closed" is unset. Finch: * The hardware cursor is updated correctly. This will be useful
--- a/ChangeLog.API Tue Jul 28 20:51:56 2009 +0000 +++ b/ChangeLog.API Thu Aug 13 17:15:06 2009 +0000 @@ -18,6 +18,9 @@ * Three Blist UI ops used to overload libpurple's built-in saving of the buddy list to blist.xml. If a UI implements these, it probably wants to add the buddies itself and not call purple_blist_load. + * Three File Transfer UI ops used to overload libpurple's use of fread + and fwrite for saving a file locally. These allow a UI to stream a + file through a socket without buffering the file on the local disk. * Jabber plugin signals (see jabber-signals.dox) * purple_account_remove_setting * purple_buddy_destroy @@ -65,6 +68,8 @@ * purple_strequal * purple_utf8_strip_unprintables * purple_util_fetch_url_request_len_with_account + * purple_xfer_prpl_ready + * purple_xfer_ui_ready * xmlnode_from_file * xmlnode_get_parent * xmlnode_set_attrib_full @@ -95,6 +100,10 @@ * status is set before emitting signals in purple_xfer_set_status. * Creating multiple distinct chats with the same name (i.e. "MSN Chat") is deprecated and will be removed in libpurple 3.0.0. + * purple_xfer_start now accepts -1 as the fd parameter if the protocol + plugin will administer the transfer itself. 0 is still accepted for + backward compatibility since older versions of libpurple will not + accept -1. Deprecated: * buddy-added and buddy-removed blist signals
--- a/ChangeLog.win32 Tue Jul 28 20:51:56 2009 +0000 +++ b/ChangeLog.win32 Thu Aug 13 17:15:06 2009 +0000 @@ -1,4 +1,5 @@ version 2.6.0 (??/??/2009): + * Added XMPP URI support. version 2.5.8 (06/27/2009): * No changes
--- a/Makefile.mingw Tue Jul 28 20:51:56 2009 +0000 +++ b/Makefile.mingw Thu Aug 13 17:15:06 2009 +0000 @@ -133,3 +133,5 @@ rm -f ./VERSION include $(PIDGIN_COMMON_TARGETS) + +.PHONY: $(PIDGIN_REVISION_H) $(PIDGIN_REVISION_RAW_TXT)
--- a/doc/Makefile.am Tue Jul 28 20:51:56 2009 +0000 +++ b/doc/Makefile.am Thu Aug 13 17:15:06 2009 +0000 @@ -32,6 +32,7 @@ gtkimhtml-signals.dox \ gtkrc-2.0 \ imgstore-signals.dox \ + jabber-signals.dox \ log-signals.dox \ notify-signals.dox \ pidgin.1.in \
--- a/doc/funniest_home_convos.txt Tue Jul 28 20:51:56 2009 +0000 +++ b/doc/funniest_home_convos.txt Thu Aug 13 17:15:06 2009 +0000 @@ -572,3 +572,42 @@ 15:46 <khc> well, there was a Grand Smiley Theme Database 15:47 <SimGuy> the GSTD sounds like a bad acronym 15:47 <khc> I realized after typing that + +-- + +(01:51:38 AM) user entered the room. +(01:52:46 AM) user: .addKeyActionListener(new KeyActionListener() onKeyPress() {if (event.geyKeyPresss().equals(Key.UP_ARROW) { inputbox.text = history.pop() }}}}}}}); +(01:52:51 AM) user: THERE, FOR **** SAKE +(01:52:53 AM) user: its 2009 +(01:53:06 AM) user: oh wait. ctrl up works +(01:53:07 AM) user: lol +(01:53:11 AM) user: yey me +(01:53:16 AM) user left the room. +(01:55:31 AM) darkrain42: Wow. +(01:58:15 AM) QuLogic: I think he failed to realize we'd have to re-write pidgin in java to do that +(01:59:44 AM) khc: history.pop() is clearly wrong too + +-- + +Some time later: +(02:41:55 AM) user entered the room. +(02:42:24 AM) user: didn't I read some idiot post, about 2 years ago, before pidgin was renamed / forked, over one dev refusing to make minimize on close? +(02:43:12 AM) QuLogic: I see you've learned to at least ask a question before jumping to random conclusions +(02:44:01 AM) user: QuLogic: :-)))))))))))))))))))))) +(02:44:12 AM) user: hey, I submitted a code patch! +(02:44:36 AM) user: now, anyway, what happened? why did I get the buddy list (empty) stealing focus, and why did it exit on close? +(02:44:40 AM) QuLogic: it's not really a patch if it's in the wrong language +(02:44:42 AM) user: I've had this argument before, in 2006 +(02:44:55 AM) user: QuLogic: simple, rewrite the rest ;-) +(02:44:58 AM) khc: pidgin never steals focus +(02:45:10 AM) khc: if it exit on close, it's because you didn't turn on the systray icon +(02:47:17 AM) user: khc - and that isn't default... why? anyway. I recall something on the matter, and I think this was the project (pre-fork?) or is this the unforked, renamed? I forget. +(02:47:42 AM) user: Whoever it was arguing about it (and font sizes I believe) was an idiot... not one of you I suppose, just making idle chit chat. +(02:47:43 AM) user: thanks +(02:47:48 AM) darkrain42: It is on by default. Some distros change that. +(02:48:00 AM) darkrain42: And I don't even know what you're arguing about at this point. +(02:48:11 AM) user: ... /leave - That command doesn't work on this protocol... /leave #pidgin ...That comm..... :-((( +(02:48:18 AM) user: darkrain42: now arguing, just remembering something +(02:48:27 AM) user left the room. +(02:49:04 AM) darkrain42: Wow. (again) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/jabber-signals.dox Thu Aug 13 17:15:06 2009 +0000 @@ -0,0 +1,125 @@ +/** @page jabber-signals Jabber Signals + + @signals + @signal jabber-receiving-iq + @signal jabber-receiving-message + @signal jabber-receiving-presence + @signal jabber-watched-iq + @signal jabber-register-namespace-watcher + @signal jabber-unregister-namespace-watcher + @signal jabber-sending-xmlnode + @endsignals + + <hr> + + @signaldef jabber-receiving-iq + @signalproto +gboolean (*iq_received)(PurpleConnection *gc, const char *type, const char *id, + const char *from, xmlnode *iq); + @endsignalproto + @signaldesc + Emitted when an XMPP IQ stanza is received. Allows a plugin to process IQ + stanzas. + @param gc The connection on which the stanza is received + @param type The IQ type ('get', 'set', 'result', or 'error') + @param id The ID attribute from the stanza. MUST NOT be NULL. + @param from The originator of the stanza. MAY BE NULL if the stanza + originated from the user's server. + @param iq The full stanza received. + @return TRUE if the plugin processed this stanza and *nobody else* should + process it. FALSE otherwise. + @endsignaldef + + @signaldef jabber-receiving-message + @signalproto +gboolean (*message_received)(PurpleConnection *gc, const char *type, + const char *id, const char *from, const char *to, + xmlnode *message); + @endsignalproto + @signaldesc + Emitted when an XMPP message stanza is received. Allows a plugin to + process message stanzas. + @param gc The connection on which the stanza is received + @param type The message type (see rfc3921 or rfc3921bis) + @param id The ID attribute from the stanza. MAY BE NULL. + @param from The originator of the stanza. MAY BE NULL if the stanza + originated from the user's server. + @param to The destination of the stanza. This is probably either the + full JID of the receiver or the receiver's bare JID. + @param message The full stanza received. + @return TRUE if the plugin processed this stanza and *nobody else* should + process it. FALSE otherwise. + @endsignaldef + + @signaldef jabber-receiving-presence + @signalproto +gboolean (*presence_received)(PurpleConnection *gc, const char *type, + const char *from, xmlnode *presence); + @endsignalproto + @signaldesc + Emitted when an XMPP presence stanza is received. Allows a plugin to process + presence stanzas. + @param gc The connection on which the stanza is received + @param type The presence type (see rfc3921 or rfc3921bis). NULL indicates + this is an "available" (i.e. online) presence. + @param from The originator of the stanza. MAY BE NULL if the stanza + originated from the user's server. + @param presence The full stanza received. + @return TRUE if the plugin processed this stanza and *nobody else* should + process it. FALSE otherwise. + @endsignaldef + + @signaldef jabber-watched-iq + @signalproto +gboolean (*watched_iq)(PurpleConnection *pc, const char *type, const char *id, + const char *from, xmlnode *child); + @endsignalproto + @signaldesc + Emitted when an IQ with a watched (child, namespace) pair is received. See + jabber-register-namespace-watcher and jabber-unregister-namespace-watcher. + @param gc The connection on which the stanza is received + @param type The IQ type ('get', 'set', 'result', or 'error') + @param id The ID attribute from the stanza. MUST NOT be NULL. + @param from The originator of the stanza. MAY BE NULL if the stanza + originated from the user's server. + @param child The child node with namespace. + @return TRUE if the plugin processed this stanza and *nobody else* should + process it. FALSE otherwise. + @endsignaldef + + @signaldef jabber-sending-xmlnode + @signalproto +void (sending_xmlnode)(PurpleConnection *gc, xmlnode **stanza); + @endsignalproto + @signaldesc + Emit this signal (@c purple_signal_emit) to send a stanza. It is preferred + to use this instead of prpl_info->send_raw. + @param gc The connectoin on which to send the stanza. + @param stanza The stanza to send. If stanza is not NULL after being sent, + the emitter should free it. + @endsignaldef + + @signaldef jabber-register-namespace-watcher + @signalproto +void (register_namespace_watcher)(const char *node, const char *namespace); + @endsignalproto + @signaldesc + Emit this signal to register your desire to have specific IQ stanzas to be + emitted via the jabber-watched-iq signal when received. + @param node The IQ child name to longer watch. + @param namespace The IQ child namespace to longer watch. + @endsignaldef + + @signaldef jabber-unregister-namespace-watcher + @signalproto +void (unregister_namespace_watcher)(const char *node, const char *namespace); + @endsignalproto + @signaldesc + Emit this signal to unregister your desire to have specific IQ stanzas to be + emitted via the jabber-watched-iq signal when received. + @param node The IQ child name to no longer watch. + @param namespace The IQ child namespace to no longer watch. + @endsignaldef + +*/ +// vim: syntax=c.doxygen tw=75 et
--- a/finch/gntaccount.c Tue Jul 28 20:51:56 2009 +0000 +++ b/finch/gntaccount.c Thu Aug 13 17:15:06 2009 +0000 @@ -489,6 +489,7 @@ GntWidget *combo, *button, *entry; GList *list, *iter; AccountEditDialog *dialog; + PurplePlugin *plugin; if (account) { @@ -532,9 +533,10 @@ ((PurplePlugin*)iter->data)->info->name); } - if (account) - gnt_combo_box_set_selected(GNT_COMBO_BOX(combo), - purple_plugins_find_with_id(purple_account_get_protocol_id(account))); + plugin = purple_plugins_find_with_id(purple_account_get_protocol_id(account)); + + if (account && plugin) + gnt_combo_box_set_selected(GNT_COMBO_BOX(combo), plugin); else gnt_combo_box_set_selected(GNT_COMBO_BOX(combo), list->data);
--- a/finch/gntblist.c Tue Jul 28 20:51:56 2009 +0000 +++ b/finch/gntblist.c Thu Aug 13 17:15:06 2009 +0000 @@ -589,6 +589,16 @@ ggblist->manager = &default_manager; } +static void destroy_list(PurpleBuddyList *list) +{ + if (ggblist == NULL) + return; + + gnt_widget_destroy(ggblist->window); + g_free(ggblist); + ggblist = NULL; +} + static gboolean remove_new_empty_group(gpointer data) { @@ -849,7 +859,7 @@ blist_show, node_update, node_remove, - NULL, + destroy_list, NULL, finch_request_add_buddy, finch_request_add_chat, @@ -3184,12 +3194,6 @@ void finch_blist_uninit() { - if (ggblist == NULL) - return; - - gnt_widget_destroy(ggblist->window); - g_free(ggblist); - ggblist = NULL; } gboolean finch_blist_get_position(int *x, int *y)
--- a/finch/gntmedia.c Tue Jul 28 20:51:56 2009 +0000 +++ b/finch/gntmedia.c Thu Aug 13 17:15:06 2009 +0000 @@ -417,11 +417,7 @@ create_default_audio_src(PurpleMedia *media, const gchar *session_id, const gchar *participant) { - GstElement *bin, *src, *volume; - GstPad *pad, *ghost; - double input_volume = purple_prefs_get_int( - "/finch/media/audio/volume/input")/10.0; - + GstElement *src; src = gst_element_factory_make("gconfaudiosrc", NULL); if (src == NULL) src = gst_element_factory_make("autoaudiosrc", NULL); @@ -436,28 +432,15 @@ "element for the default audio source.\n"); return NULL; } - - bin = gst_bin_new("finchdefaultaudiosrc"); - volume = gst_element_factory_make("volume", "purpleaudioinputvolume"); - g_object_set(volume, "volume", input_volume, NULL); - gst_bin_add_many(GST_BIN(bin), src, volume, NULL); - gst_element_link(src, volume); - pad = gst_element_get_pad(volume, "src"); - ghost = gst_ghost_pad_new("ghostsrc", pad); - gst_element_add_pad(bin, ghost); - - return bin; + gst_element_set_name(src, "finchdefaultaudiosrc"); + return src; } static GstElement * create_default_audio_sink(PurpleMedia *media, const gchar *session_id, const gchar *participant) { - GstElement *bin, *sink, *volume, *queue; - GstPad *pad, *ghost; - double output_volume = purple_prefs_get_int( - "/finch/media/audio/volume/output")/10.0; - + GstElement *sink; sink = gst_element_factory_make("gconfaudiosink", NULL); if (sink == NULL) sink = gst_element_factory_make("autoaudiosink",NULL); @@ -466,19 +449,7 @@ "element for the default audio sink.\n"); return NULL; } - - bin = gst_bin_new("finchdefaultaudiosink"); - volume = gst_element_factory_make("volume", "purpleaudiooutputvolume"); - g_object_set(volume, "volume", output_volume, NULL); - queue = gst_element_factory_make("queue", NULL); - gst_bin_add_many(GST_BIN(bin), sink, volume, queue, NULL); - gst_element_link(volume, sink); - gst_element_link(queue, volume); - pad = gst_element_get_pad(queue, "sink"); - ghost = gst_ghost_pad_new("ghostsink", pad); - gst_element_add_pad(bin, ghost); - - return bin; + return sink; } #endif /* USE_VV */ @@ -516,12 +487,6 @@ purple_debug_info("gntmedia", "Registering media element types\n"); purple_media_manager_set_active_element(manager, default_audio_src); purple_media_manager_set_active_element(manager, default_audio_sink); - - purple_prefs_add_none("/finch/media"); - purple_prefs_add_none("/finch/media/audio"); - purple_prefs_add_none("/finch/media/audio/volume"); - purple_prefs_add_int("/finch/media/audio/volume/input", 10); - purple_prefs_add_int("/finch/media/audio/volume/output", 10); #endif }
--- a/libpurple/certificate.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/certificate.c Thu Aug 13 17:15:06 2009 +0000 @@ -386,7 +386,6 @@ scheme = crt->scheme; - /* TODO: Instead of failing, maybe use get_subject_name and strcmp? */ g_return_val_if_fail(scheme->check_subject_name, FALSE); return (scheme->check_subject_name)(crt, name); @@ -1318,20 +1317,113 @@ g_byte_array_free(cached_fpr, TRUE); } +/* + * This is called from two points in x509_tls_cached_unknown_peer below + * once we've verified the signature chain is valid. Now we need to verify + * the subject name of the certificate. + */ +static void +x509_tls_cached_check_subject_name(PurpleCertificateVerificationRequest *vrq, + gboolean had_ca_pool) +{ + PurpleCertificatePool *tls_peers; + PurpleCertificate *peer_crt; + GList *chain = vrq->cert_chain; + + peer_crt = (PurpleCertificate *) chain->data; + + /* Last, check that the hostname matches */ + if ( ! purple_certificate_check_subject_name(peer_crt, + vrq->subject_name) ) { + gchar *sn = purple_certificate_get_subject_name(peer_crt); + + purple_debug_error("certificate/x509/tls_cached", + "Name mismatch: Certificate given for %s " + "has a name of %s\n", + vrq->subject_name, sn); + + if (had_ca_pool) { + /* Prompt the user to authenticate the certificate */ + /* TODO: Provide the user with more guidance about why he is + being prompted */ + /* vrq will be completed by user_auth */ + gchar *msg; + msg = g_strdup_printf(_("The certificate presented by \"%s\" " + "claims to be from \"%s\" instead. " + "This could mean that you are not " + "connecting to the service you " + "believe you are."), + vrq->subject_name, sn); + + x509_tls_cached_user_auth(vrq, msg); + g_free(msg); + } else { + /* Had no CA pool, so couldn't verify the chain *and* + * the subject name isn't valid. + * I think this is bad enough to warrant a fatal error. It's + * not likely anyway... + */ + purple_notify_error(NULL, /* TODO: Probably wrong. */ + _("SSL Certificate Error"), + _("Invalid certificate chain"), + _("You have no database of root certificates, so " + "this certificate cannot be validated.")); + } + + g_free(sn); + return; + } /* if (name mismatch) */ + + if (!had_ca_pool) { + /* The subject name is correct, but we weren't able to verify the + * chain because there was no pool of root CAs found. Prompt the user + * to validate it. + */ + + /* vrq will be completed by user_auth */ + x509_tls_cached_user_auth(vrq,_("You have no database of root " + "certificates, so this " + "certificate cannot be " + "validated.")); + return; + } + + /* If we reach this point, the certificate is good. */ + /* Look up the local cache and store it there for future use */ + tls_peers = purple_certificate_find_pool(x509_tls_cached.scheme_name, + "tls_peers"); + + if (tls_peers) { + if (!purple_certificate_pool_store(tls_peers,vrq->subject_name, + peer_crt) ) { + purple_debug_error("certificate/x509/tls_cached", + "FAILED to cache peer certificate\n"); + } + } else { + purple_debug_error("certificate/x509/tls_cached", + "Unable to locate tls_peers certificate " + "cache.\n"); + } + + /* Whew! Done! */ + purple_certificate_verify_complete(vrq, PURPLE_CERTIFICATE_VALID); + +} + /* For when we've never communicated with this party before */ /* TODO: Need ways to specify possibly multiple problems with a cert, or at - least reprioritize them. For example, maybe the signature ought to be - checked BEFORE the hostname checking? - Stu thinks we should check the signature before the name, so we do now. - The above TODO still stands. */ + least reprioritize them. + */ static void x509_tls_cached_unknown_peer(PurpleCertificateVerificationRequest *vrq) { - PurpleCertificatePool *ca, *tls_peers; + PurpleCertificatePool *ca; PurpleCertificate *peer_crt; + PurpleCertificate *ca_crt, *end_crt; PurpleCertificate *failing_crt; GList *chain = vrq->cert_chain; - gboolean chain_validated = FALSE; + GByteArray *last_fpr, *ca_fpr; + gchar *ca_id; peer_crt = (PurpleCertificate *) chain->data; @@ -1361,10 +1453,10 @@ ca = purple_certificate_find_pool(x509_tls_cached.scheme_name, "ca"); /* Next, check that the certificate chain is valid */ - if (purple_certificate_check_signature_chain_with_failing(chain, - &failing_crt)) - chain_validated = TRUE; - else { + if (!purple_certificate_check_signature_chain_with_failing(chain, + &failing_crt)) + { + gboolean chain_validated = FALSE; /* * Check if the failing certificate is in the CA store. If it is, then * consider this fully validated. This works around issues with some @@ -1399,7 +1491,9 @@ * If we get here, either the cert matched the stuff right above * or it didn't, in which case we give up and complain to the user. */ - if (!chain_validated) { + if (chain_validated) { + x509_tls_cached_check_subject_name(vrq, TRUE); + } else { /* TODO: Tell the user where the chain broke? */ /* TODO: This error will hopelessly confuse any non-elite user. */ @@ -1421,156 +1515,100 @@ /* Okay, we're done here */ purple_certificate_verify_complete(vrq, PURPLE_CERTIFICATE_INVALID); - return; } + + return; } /* if (signature chain not good) */ /* If, for whatever reason, there is no Certificate Authority pool - loaded, we will simply present it to the user for checking. */ + loaded, we'll verify the subject name and then warn about thsi. */ if ( !ca ) { purple_debug_error("certificate/x509/tls_cached", "No X.509 Certificate Authority pool " "could be found!\n"); + x509_tls_cached_check_subject_name(vrq, FALSE); + return; + } + + end_crt = g_list_last(chain)->data; + + /* Attempt to look up the last certificate's issuer */ + ca_id = purple_certificate_get_issuer_unique_id(end_crt); + purple_debug_info("certificate/x509/tls_cached", + "Checking for a CA with DN=%s\n", + ca_id); + ca_crt = purple_certificate_pool_retrieve(ca, ca_id); + if ( NULL == ca_crt ) { + purple_debug_warning("certificate/x509/tls_cached", + "Certificate Authority with DN='%s' not " + "found. I'll prompt the user, I guess.\n", + ca_id); + g_free(ca_id); /* vrq will be completed by user_auth */ - x509_tls_cached_user_auth(vrq,_("You have no database of root " - "certificates, so this " - "certificate cannot be " - "validated.")); + x509_tls_cached_user_auth(vrq,_("The root certificate this " + "one claims to be issued by " + "is unknown to Pidgin.")); return; } - if (!chain_validated) { - GByteArray *last_fpr, *ca_fpr; - PurpleCertificate *ca_crt, *end_crt; - gchar *ca_id; - - end_crt = g_list_last(chain)->data; + g_free(ca_id); - /* Attempt to look up the last certificate's issuer */ - ca_id = purple_certificate_get_issuer_unique_id(end_crt); - purple_debug_info("certificate/x509/tls_cached", - "Checking for a CA with DN=%s\n", - ca_id); - ca_crt = purple_certificate_pool_retrieve(ca, ca_id); - if ( NULL == ca_crt ) { - purple_debug_warning("certificate/x509/tls_cached", - "Certificate Authority with DN='%s' not " - "found. I'll prompt the user, I guess.\n", - ca_id); - g_free(ca_id); - /* vrq will be completed by user_auth */ - x509_tls_cached_user_auth(vrq,_("The root certificate this " - "one claims to be issued by " - "is unknown to Pidgin.")); - return; - } - - g_free(ca_id); + /* + * Check the fingerprints; if they match, then this certificate *is* one + * of the designated "trusted roots", and we don't need to verify the + * signature. This is good because some of the older roots are self-signed + * with bad hash algorithms that we don't want to allow in any other + * circumstances (one of Verisign's root CAs is self-signed with MD2). + * + * If the fingerprints don't match, we'll fall back to checking the + * signature. + * + * GnuTLS doesn't seem to include the final root in the verification + * list, so this check will never succeed. NSS *does* include it in + * the list, so here we are. + */ + last_fpr = purple_certificate_get_fingerprint_sha1(end_crt); + ca_fpr = purple_certificate_get_fingerprint_sha1(ca_crt); - /* - * Check the fingerprints; if they match, then this certificate *is* one - * of the designated "trusted roots", and we don't need to verify the - * signature. This is good because some of the older roots are self-signed - * with bad hash algorithms that we don't want to allow in any other - * circumstances (one of Verisign's root CAs is self-signed with MD2). - * - * If the fingerprints don't match, we'll fall back to checking the - * signature. - * - * GnuTLS doesn't seem to include the final root in the verification - * list, so this check will never succeed. NSS *does* include it in - * the list, so here we are. - */ - last_fpr = purple_certificate_get_fingerprint_sha1(end_crt); - ca_fpr = purple_certificate_get_fingerprint_sha1(ca_crt); + if ( !byte_arrays_equal(last_fpr, ca_fpr) && + !purple_certificate_signed_by(end_crt, ca_crt) ) + { + /* TODO: If signed_by ever returns a reason, maybe mention + that, too. */ + /* TODO: Also mention the CA involved. While I could do this + now, a full DN is a little much with which to assault the + user's poor, leaky eyes. */ + /* TODO: This error message makes my eyes cross, and I wrote it */ + gchar * secondary = + g_strdup_printf(_("The certificate chain presented by " + "%s does not have a valid digital " + "signature from the Certificate " + "Authority from which it claims to " + "have a signature."), + vrq->subject_name); - if ( !byte_arrays_equal(last_fpr, ca_fpr) && - !purple_certificate_signed_by(end_crt, ca_crt) ) - { - /* TODO: If signed_by ever returns a reason, maybe mention - that, too. */ - /* TODO: Also mention the CA involved. While I could do this - now, a full DN is a little much with which to assault the - user's poor, leaky eyes. */ - /* TODO: This error message makes my eyes cross, and I wrote it */ - gchar * secondary = - g_strdup_printf(_("The certificate chain presented by " - "%s does not have a valid digital " - "signature from the Certificate " - "Authority from which it claims to " - "have a signature."), - vrq->subject_name); + purple_notify_error(NULL, /* TODO: Probably wrong */ + _("SSL Certificate Error"), + _("Invalid certificate authority" + " signature"), + secondary); + g_free(secondary); - purple_notify_error(NULL, /* TODO: Probably wrong */ - _("SSL Certificate Error"), - _("Invalid certificate authority" - " signature"), - secondary); - g_free(secondary); + /* Signal "bad cert" */ + purple_certificate_verify_complete(vrq, + PURPLE_CERTIFICATE_INVALID); - /* Signal "bad cert" */ - purple_certificate_verify_complete(vrq, - PURPLE_CERTIFICATE_INVALID); - - purple_certificate_destroy(ca_crt); - g_byte_array_free(ca_fpr, TRUE); - g_byte_array_free(last_fpr, TRUE); - return; - } /* if (CA signature not good) */ - + purple_certificate_destroy(ca_crt); g_byte_array_free(ca_fpr, TRUE); g_byte_array_free(last_fpr, TRUE); - } - - /* Last, check that the hostname matches */ - if ( ! purple_certificate_check_subject_name(peer_crt, - vrq->subject_name) ) { - gchar *sn = purple_certificate_get_subject_name(peer_crt); - gchar *msg; - - purple_debug_error("certificate/x509/tls_cached", - "Name mismatch: Certificate given for %s " - "has a name of %s\n", - vrq->subject_name, sn); - - /* Prompt the user to authenticate the certificate */ - /* TODO: Provide the user with more guidance about why he is - being prompted */ - /* vrq will be completed by user_auth */ - msg = g_strdup_printf(_("The certificate presented by \"%s\" " - "claims to be from \"%s\" instead. " - "This could mean that you are not " - "connecting to the service you " - "believe you are."), - vrq->subject_name, sn); + return; + } /* if (CA signature not good) */ - x509_tls_cached_user_auth(vrq,msg); - - g_free(sn); - g_free(msg); - return; - } /* if (name mismatch) */ - - /* If we reach this point, the certificate is good. */ - /* Look up the local cache and store it there for future use */ - tls_peers = purple_certificate_find_pool(x509_tls_cached.scheme_name, - "tls_peers"); + g_byte_array_free(ca_fpr, TRUE); + g_byte_array_free(last_fpr, TRUE); - if (tls_peers) { - if (!purple_certificate_pool_store(tls_peers,vrq->subject_name, - peer_crt) ) { - purple_debug_error("certificate/x509/tls_cached", - "FAILED to cache peer certificate\n"); - } - } else { - purple_debug_error("certificate/x509/tls_cached", - "Unable to locate tls_peers certificate " - "cache.\n"); - } - - /* Whew! Done! */ - purple_certificate_verify_complete(vrq, PURPLE_CERTIFICATE_VALID); + x509_tls_cached_check_subject_name(vrq, TRUE); } static void
--- a/libpurple/cipher.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/cipher.c Thu Aug 13 17:15:06 2009 +0000 @@ -2727,8 +2727,6 @@ cipher = PURPLE_CIPHER(l->data); purple_ciphers_unregister_cipher(cipher); - - ciphers = g_list_remove(ciphers, cipher); } g_list_free(ciphers);
--- a/libpurple/connection.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/connection.c Thu Aug 13 17:15:06 2009 +0000 @@ -578,6 +578,9 @@ gc->wants_to_die = purple_connection_error_is_fatal (reason); + purple_debug_info("connection", "Connection error on %p (reason: %u description: %s)\n", + gc, reason, description); + ops = purple_connections_get_ui_ops(); if (ops != NULL)
--- a/libpurple/desktopitem.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/desktopitem.c Thu Aug 13 17:15:06 2009 +0000 @@ -831,11 +831,10 @@ static char * try_english_key (PurpleDesktopItem *item, const char *key) { - char *str; + char *str = NULL; char *locales[] = { "en_US", "en_GB", "en_AU", "en", NULL }; int i; - str = NULL; for (i = 0; locales[i] != NULL && str == NULL; i++) { str = g_strdup (lookup_locale (item, key, locales[i])); }
--- a/libpurple/ft.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/ft.c Thu Aug 13 17:15:06 2009 +0000 @@ -40,8 +40,35 @@ static PurpleXferUiOps *xfer_ui_ops = NULL; static GList *xfers; +/* + * A hack to store more data since we can't extend the size of PurpleXfer + * easily. + */ +static GHashTable *xfers_data = NULL; + +typedef struct _PurpleXferPrivData { + /* + * Used to moderate the file transfer when either the read/write ui_ops are + * set or fd is not set. In those cases, the UI/prpl call the respective + * function, which is somewhat akin to a fd watch being triggered. + */ + enum { + PURPLE_XFER_READY_NONE = 0x0, + PURPLE_XFER_READY_UI = 0x1, + PURPLE_XFER_READY_PRPL = 0x2, + } ready; +} PurpleXferPrivData; + static int purple_xfer_choose_file(PurpleXfer *xfer); +static void +purple_xfer_priv_data_destroy(gpointer data) +{ + PurpleXferPrivData *priv = data; + + g_free(priv); +} + GList * purple_xfers_get_all() { @@ -53,6 +80,7 @@ { PurpleXfer *xfer; PurpleXferUiOps *ui_ops; + PurpleXferPrivData *priv; g_return_val_if_fail(type != PURPLE_XFER_UNKNOWN, NULL); g_return_val_if_fail(account != NULL, NULL); @@ -70,6 +98,11 @@ xfer->current_buffer_size = FT_INITIAL_BUFFER_SIZE; xfer->fd = -1; + priv = g_new0(PurpleXferPrivData, 1); + priv->ready = PURPLE_XFER_READY_NONE; + + g_hash_table_insert(xfers_data, xfer, priv); + ui_ops = purple_xfer_get_ui_ops(xfer); if (ui_ops != NULL && ui_ops->new_xfer != NULL) @@ -101,11 +134,13 @@ g_free(xfer->filename); g_free(xfer->remote_ip); g_free(xfer->local_filename); + + g_hash_table_remove(xfers_data, xfer); g_free(xfer->thumbnail_data); PURPLE_DBUS_UNREGISTER_POINTER(xfer); + xfers = g_list_remove(xfers, xfer); g_free(xfer); - xfers = g_list_remove(xfers, xfer); } void @@ -133,6 +168,9 @@ { g_return_if_fail(xfer != NULL); + if (xfer->status == status) + return; + xfer->status = status; if(xfer->type == PURPLE_XFER_SEND) { @@ -487,13 +525,16 @@ if (type == PURPLE_XFER_SEND) { /* Sending a file */ /* Check the filename. */ + PurpleXferUiOps *ui_ops; + ui_ops = purple_xfer_get_ui_ops(xfer); + #ifdef _WIN32 if (g_strrstr(filename, "../") || g_strrstr(filename, "..\\")) #else if (g_strrstr(filename, "../")) #endif { - char *utf8 = g_filename_to_utf8(filename, -1, NULL, NULL, NULL); + utf8 = g_filename_to_utf8(filename, -1, NULL, NULL, NULL); msg = g_strdup_printf(_("%s is not a valid filename.\n"), utf8); purple_xfer_error(type, account, xfer->who, msg); @@ -504,15 +545,20 @@ return; } - if (g_stat(filename, &st) == -1) { - purple_xfer_show_file_error(xfer, filename); - purple_xfer_unref(xfer); - return; + if (ui_ops == NULL || (ui_ops->ui_read == NULL && ui_ops->ui_write == NULL)) { + if (g_stat(filename, &st) == -1) { + purple_xfer_show_file_error(xfer, filename); + purple_xfer_unref(xfer); + return; + } + + purple_xfer_set_local_filename(xfer, filename); + purple_xfer_set_size(xfer, st.st_size); + } else { + utf8 = g_strdup(filename); + purple_xfer_set_local_filename(xfer, filename); } - purple_xfer_set_local_filename(xfer, filename); - purple_xfer_set_size(xfer, st.st_size); - base = g_path_get_basename(filename); utf8 = g_filename_to_utf8(base, -1, NULL, NULL, NULL); g_free(base); @@ -521,7 +567,6 @@ msg = g_strdup_printf(_("Offering to send %s to %s"), utf8, buddy ? purple_buddy_get_alias(buddy) : xfer->who); g_free(utf8); - purple_xfer_conversation_write(xfer, msg, FALSE); g_free(msg); } @@ -889,9 +934,6 @@ if (xfer->ops.read != NULL) { r = (xfer->ops.read)(buffer, xfer); - if ((purple_xfer_get_size(xfer) > 0) && - ((purple_xfer_get_bytes_sent(xfer)+r) >= purple_xfer_get_size(xfer))) - purple_xfer_set_completed(xfer, TRUE); } else { *buffer = g_malloc0(s); @@ -901,9 +943,6 @@ r = 0; else if (r < 0) r = -1; - else if ((purple_xfer_get_size(xfer) > 0) && - ((purple_xfer_get_bytes_sent(xfer)+r) >= purple_xfer_get_size(xfer))) - purple_xfer_set_completed(xfer, TRUE); else if (r == 0) r = -1; } @@ -944,29 +983,39 @@ } static void -transfer_cb(gpointer data, gint source, PurpleInputCondition condition) +do_transfer(PurpleXfer *xfer) { PurpleXferUiOps *ui_ops; - PurpleXfer *xfer = (PurpleXfer *)data; guchar *buffer = NULL; gssize r = 0; - if (condition & PURPLE_INPUT_READ) { + ui_ops = purple_xfer_get_ui_ops(xfer); + + if (xfer->type == PURPLE_XFER_RECEIVE) { r = purple_xfer_read(xfer, &buffer); if (r > 0) { - const size_t wc = fwrite(buffer, 1, r, xfer->dest_fp); + size_t wc; + if (ui_ops && ui_ops->ui_write) + wc = ui_ops->ui_write(xfer, buffer, r); + else + wc = fwrite(buffer, 1, r, xfer->dest_fp); + if (wc != r) { purple_debug_error("filetransfer", "Unable to write whole buffer.\n"); purple_xfer_cancel_local(xfer); + g_free(buffer); return; } + + if ((purple_xfer_get_size(xfer) > 0) && + ((purple_xfer_get_bytes_sent(xfer)+r) >= purple_xfer_get_size(xfer))) + purple_xfer_set_completed(xfer, TRUE); } else if(r < 0) { purple_xfer_cancel_remote(xfer); + g_free(buffer); return; } - } - - if (condition & PURPLE_INPUT_WRITE) { + } else if (xfer->type == PURPLE_XFER_SEND) { size_t result; size_t s = MIN(purple_xfer_get_bytes_remaining(xfer), xfer->current_buffer_size); @@ -980,26 +1029,53 @@ return; } - buffer = g_malloc0(s); + if (ui_ops && ui_ops->ui_read) { + gssize tmp = ui_ops->ui_read(xfer, &buffer, s); + if (tmp == 0) { + /* + * UI isn't ready to send data. It will call + * purple_xfer_ui_ready when ready, which sets back up this + * watcher. + */ + if (xfer->watcher != 0) { + purple_timeout_remove(xfer->watcher); + xfer->watcher = 0; + } - result = fread(buffer, 1, s, xfer->dest_fp); - if (result != s) { - purple_debug_error("filetransfer", "Unable to read whole buffer.\n"); - purple_xfer_cancel_remote(xfer); - g_free(buffer); - return; + return; + } else if (tmp < 0) { + purple_debug_error("filetransfer", "Unable to read whole buffer.\n"); + purple_xfer_cancel_local(xfer); + return; + } + + result = tmp; + } else { + buffer = g_malloc0(s); + result = fread(buffer, 1, s, xfer->dest_fp); + if (result != s) { + purple_debug_error("filetransfer", "Unable to read whole buffer.\n"); + purple_xfer_cancel_local(xfer); + g_free(buffer); + return; + } } /* Write as much as we're allowed to. */ - r = purple_xfer_write(xfer, buffer, s); + r = purple_xfer_write(xfer, buffer, result); if (r == -1) { purple_xfer_cancel_remote(xfer); g_free(buffer); return; - } else if (r < s) { - /* We have to seek back in the file now. */ - fseek(xfer->dest_fp, r - s, SEEK_CUR); + } else if (r < result) { + if (ui_ops == NULL || (ui_ops->ui_read == NULL && ui_ops->ui_write == NULL)) { + /* We have to seek back in the file now. */ + fseek(xfer->dest_fp, r - s, SEEK_CUR); + } + else { + ui_ops->data_not_sent(xfer, buffer + r, result - r); + } } else { /* * We managed to write the entire buffer. This means our @@ -1021,8 +1097,6 @@ g_free(buffer); - ui_ops = purple_xfer_get_ui_ops(xfer); - if (ui_ops != NULL && ui_ops->update_progress != NULL) ui_ops->update_progress(xfer, purple_xfer_get_progress(xfer)); @@ -1033,22 +1107,45 @@ } static void +transfer_cb(gpointer data, gint source, PurpleInputCondition condition) +{ + PurpleXfer *xfer = data; + + if (xfer->dest_fp == NULL) { + /* The UI is moderating its side manually */ + PurpleXferPrivData *priv = g_hash_table_lookup(xfers_data, xfer); + if (0 == (priv->ready & PURPLE_XFER_READY_UI)) { + priv->ready |= PURPLE_XFER_READY_PRPL; + + purple_input_remove(xfer->watcher); + xfer->watcher = 0; + return; + } + } + + do_transfer(xfer); +} + +static void begin_transfer(PurpleXfer *xfer, PurpleInputCondition cond) { PurpleXferType type = purple_xfer_get_type(xfer); + PurpleXferUiOps *ui_ops = purple_xfer_get_ui_ops(xfer); - xfer->dest_fp = g_fopen(purple_xfer_get_local_filename(xfer), - type == PURPLE_XFER_RECEIVE ? "wb" : "rb"); + if (ui_ops == NULL || (ui_ops->ui_read == NULL && ui_ops->ui_write == NULL)) { + xfer->dest_fp = g_fopen(purple_xfer_get_local_filename(xfer), + type == PURPLE_XFER_RECEIVE ? "wb" : "rb"); - if (xfer->dest_fp == NULL) { - purple_xfer_show_file_error(xfer, purple_xfer_get_local_filename(xfer)); - purple_xfer_cancel_local(xfer); - return; + if (xfer->dest_fp == NULL) { + purple_xfer_show_file_error(xfer, purple_xfer_get_local_filename(xfer)); + purple_xfer_cancel_local(xfer); + return; + } + + fseek(xfer->dest_fp, xfer->bytes_sent, SEEK_SET); } - fseek(xfer->dest_fp, xfer->bytes_sent, SEEK_SET); - - if (xfer->fd) + if (xfer->fd != -1) xfer->watcher = purple_input_add(xfer->fd, cond, transfer_cb, xfer); xfer->start_time = time(NULL); @@ -1073,6 +1170,54 @@ } void +purple_xfer_ui_ready(PurpleXfer *xfer) +{ + PurpleInputCondition cond; + PurpleXferType type; + PurpleXferPrivData *priv; + + g_return_if_fail(xfer != NULL); + + priv = g_hash_table_lookup(xfers_data, xfer); + priv->ready |= PURPLE_XFER_READY_UI; + + if (0 == (priv->ready & PURPLE_XFER_READY_PRPL)) + return; + + type = purple_xfer_get_type(xfer); + if (type == PURPLE_XFER_SEND) + cond = PURPLE_INPUT_WRITE; + else /* if (type == PURPLE_XFER_RECEIVE) */ + cond = PURPLE_INPUT_READ; + + if (xfer->watcher == 0 && xfer->fd != -1) + xfer->watcher = purple_input_add(xfer->fd, cond, transfer_cb, xfer); + + priv->ready = PURPLE_XFER_READY_NONE; + + do_transfer(xfer); +} + +void +purple_xfer_prpl_ready(PurpleXfer *xfer) +{ + PurpleXferPrivData *priv; + + g_return_if_fail(xfer != NULL); + + priv = g_hash_table_lookup(xfers_data, xfer); + priv->ready |= PURPLE_XFER_READY_PRPL; + + /* I don't think fwrite/fread are ever *not* ready */ + if (xfer->dest_fp == NULL && 0 == (priv->ready & PURPLE_XFER_READY_UI)) + return; + + priv->ready = PURPLE_XFER_READY_NONE; + + do_transfer(xfer); +} + +void purple_xfer_start(PurpleXfer *xfer, int fd, const char *ip, unsigned int port) { @@ -1086,6 +1231,13 @@ purple_xfer_set_status(xfer, PURPLE_XFER_STATUS_STARTED); + /* + * FIXME 3.0.0 -- there's too much broken code depending on fd == 0 + * meaning "don't use a real fd" + */ + if (fd == 0) + fd = -1; + if (type == PURPLE_XFER_RECEIVE) { cond = PURPLE_INPUT_READ; @@ -1132,7 +1284,7 @@ xfer->watcher = 0; } - if (xfer->fd != 0) + if (xfer->fd != -1) close(xfer->fd); if (xfer->dest_fp != NULL) { @@ -1195,7 +1347,7 @@ xfer->watcher = 0; } - if (xfer->fd != 0) + if (xfer->fd != -1) close(xfer->fd); if (xfer->dest_fp != NULL) { @@ -1260,7 +1412,7 @@ xfer->watcher = 0; } - if (xfer->fd != 0) + if (xfer->fd != -1) close(xfer->fd); if (xfer->dest_fp != NULL) { @@ -1359,6 +1511,9 @@ purple_xfers_init(void) { void *handle = purple_xfers_get_handle(); + xfers_data = g_hash_table_new_full(g_direct_hash, g_direct_equal, + NULL, purple_xfer_priv_data_destroy); + /* register signals */ purple_signal_register(handle, "file-recv-accept", purple_marshal_VOID__POINTER, NULL, 1, @@ -1405,6 +1560,9 @@ purple_signals_disconnect_by_handle(handle); purple_signals_unregister_by_instance(handle); + + g_hash_table_destroy(xfers_data); + xfers_data = NULL; } void
--- a/libpurple/ft.h Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/ft.h Thu Aug 13 17:15:06 2009 +0000 @@ -79,8 +79,6 @@ void (*add_thumbnail)(PurpleXfer *xfer); void (*_purple_reserved1)(void); - void (*_purple_reserved2)(void); - void (*_purple_reserved3)(void); } PurpleXferUiOps; /** @@ -132,7 +130,6 @@ gssize (*read)(guchar **buffer, PurpleXfer *xfer); gssize (*write)(const guchar *buffer, size_t size, PurpleXfer *xfer); void (*ack)(PurpleXfer *xfer, const guchar *buffer, size_t size); - } ops; PurpleXferUiOps *ui_ops; /**< UI-specific operations. */ @@ -551,6 +548,12 @@ * file receive transfer. On send, @a fd must be specified, and * @a ip and @a port are ignored. * + * Prior to libpurple 2.6.0, passing '0' to @a fd was special-cased to + * allow the protocol plugin to facilitate the file transfer itself. As of + * 2.6.0, this is supported (for backward compatibility), but will be + * removed in libpurple 3.0.0. If a prpl detects that the running libpurple + * is running 2.6.0 or higher, it should use the invalid fd '-1'. + * * @param xfer The file transfer. * @param fd The file descriptor for the socket. * @param ip The IP address to connect to. @@ -620,41 +623,6 @@ */ void purple_xfer_conversation_write(PurpleXfer *xfer, char *message, gboolean is_error); -/** - * Gets the thumbnail data for a transfer - * - * @param xfer The file transfer to get the thumbnail for - * @return The thumbnail data, or NULL if there is no thumbnail - */ -const void *purple_xfer_get_thumbnail_data(const PurpleXfer *xfer); - -/** - * Gets the thumbnail size for a transfer - * - * @param xfer The file transfer to get the thumbnail size for - * @return The size, in bytes of the file transfer's thumbnail - */ -gsize purple_xfer_get_thumbnail_size(const PurpleXfer *xfer); - - -/** - * Sets the thumbnail data for a transfer - * - * @param xfer The file transfer to set the data for - * @param thumbnail A pointer to the thumbnail data, this will be copied - * @param size The size in bytes of the passed in thumbnail data - */ -void purple_xfer_set_thumbnail(PurpleXfer *xfer, gconstpointer thumbnail, - gsize size); - -/** - * Prepare a thumbnail for a transfer (if the UI supports it) - * will be no-op in case the UI doesn't implement thumbnail creation - * - * @param xfer The file transfer to create a thumbnail for - */ -void purple_xfer_prepare_thumbnail(PurpleXfer *xfer); - /*@}*/ /**************************************************************************/
--- a/libpurple/log.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/log.c Thu Aug 13 17:15:06 2009 +0000 @@ -1375,6 +1375,7 @@ char *image_corrected_msg; char *date; char *header; + char *escaped_from; PurplePlugin *plugin = purple_find_prpl(purple_account_get_protocol_id(log->account)); PurpleLogCommonLoggerData *data = log->logger_data; gsize written = 0; @@ -1413,6 +1414,8 @@ if(!data->file) return 0; + escaped_from = g_markup_escape_text(from, -1); + image_corrected_msg = convert_image_tags(log, message); purple_markup_html_to_xhtml(image_corrected_msg, &msg_fixed, NULL); @@ -1434,34 +1437,35 @@ written += fprintf(data->file, "<font color=\"#FF0000\"><font size=\"2\">(%s)</font><b> %s</b></font><br/>\n", date, msg_fixed); else if (type & PURPLE_MESSAGE_WHISPER) written += fprintf(data->file, "<font color=\"#6C2585\"><font size=\"2\">(%s)</font><b> %s:</b></font> %s<br/>\n", - date, from, msg_fixed); + date, escaped_from, msg_fixed); else if (type & PURPLE_MESSAGE_AUTO_RESP) { if (type & PURPLE_MESSAGE_SEND) - written += fprintf(data->file, _("<font color=\"#16569E\"><font size=\"2\">(%s)</font> <b>%s <AUTO-REPLY>:</b></font> %s<br/>\n"), date, from, msg_fixed); + written += fprintf(data->file, _("<font color=\"#16569E\"><font size=\"2\">(%s)</font> <b>%s <AUTO-REPLY>:</b></font> %s<br/>\n"), date, escaped_from, msg_fixed); else if (type & PURPLE_MESSAGE_RECV) - written += fprintf(data->file, _("<font color=\"#A82F2F\"><font size=\"2\">(%s)</font> <b>%s <AUTO-REPLY>:</b></font> %s<br/>\n"), date, from, msg_fixed); + written += fprintf(data->file, _("<font color=\"#A82F2F\"><font size=\"2\">(%s)</font> <b>%s <AUTO-REPLY>:</b></font> %s<br/>\n"), date, escaped_from, msg_fixed); } else if (type & PURPLE_MESSAGE_RECV) { if(purple_message_meify(msg_fixed, -1)) written += fprintf(data->file, "<font color=\"#062585\"><font size=\"2\">(%s)</font> <b>***%s</b></font> %s<br/>\n", - date, from, msg_fixed); + date, escaped_from, msg_fixed); else written += fprintf(data->file, "<font color=\"#A82F2F\"><font size=\"2\">(%s)</font> <b>%s:</b></font> %s<br/>\n", - date, from, msg_fixed); + date, escaped_from, msg_fixed); } else if (type & PURPLE_MESSAGE_SEND) { if(purple_message_meify(msg_fixed, -1)) written += fprintf(data->file, "<font color=\"#062585\"><font size=\"2\">(%s)</font> <b>***%s</b></font> %s<br/>\n", - date, from, msg_fixed); + date, escaped_from, msg_fixed); else written += fprintf(data->file, "<font color=\"#16569E\"><font size=\"2\">(%s)</font> <b>%s:</b></font> %s<br/>\n", - date, from, msg_fixed); + date, escaped_from, msg_fixed); } else { purple_debug_error("log", "Unhandled message type.\n"); written += fprintf(data->file, "<font size=\"2\">(%s)</font><b> %s:</b></font> %s<br/>\n", - date, from, msg_fixed); + date, escaped_from, msg_fixed); } } g_free(date); g_free(msg_fixed); + g_free(escaped_from); fflush(data->file); return written;
--- a/libpurple/marshallers.list Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/marshallers.list Thu Aug 13 17:15:06 2009 +0000 @@ -1,5 +1,6 @@ VOID:POINTER,POINTER,OBJECT BOOLEAN:OBJECT,POINTER,STRING VOID:STRING,STRING +VOID:STRING,STRING,DOUBLE VOID:ENUM,STRING,STRING VOID:ENUM,STRING,STRING,BOOLEAN
--- a/libpurple/media.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/media.c Thu Aug 13 17:15:06 2009 +0000 @@ -94,6 +94,8 @@ FsStream *stream; GstElement *src; GstElement *tee; + GstElement *volume; + GstElement *level; GList *local_candidates; GList *remote_candidates; @@ -157,9 +159,9 @@ enum { S_ERROR, - ACCEPTED, CANDIDATES_PREPARED, CODECS_CHANGED, + LEVEL, NEW_CANDIDATE, STATE_CHANGED, STREAM_INFO, @@ -272,6 +274,10 @@ "PURPLE_MEDIA_INFO_MUTE", "mute" }, { PURPLE_MEDIA_INFO_UNMUTE, "PURPLE_MEDIA_INFO_UNMUTE", "unmute" }, + { PURPLE_MEDIA_INFO_PAUSE, + "PURPLE_MEDIA_INFO_PAUSE", "pause" }, + { PURPLE_MEDIA_INFO_UNPAUSE, + "PURPLE_MEDIA_INFO_UNPAUSE", "unpause" }, { PURPLE_MEDIA_INFO_HOLD, "PURPLE_MEDIA_INFO_HOLD", "hold" }, { PURPLE_MEDIA_INFO_UNHOLD, @@ -332,10 +338,6 @@ G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING); - purple_media_signals[ACCEPTED] = g_signal_new("accepted", G_TYPE_FROM_CLASS(klass), - G_SIGNAL_RUN_LAST, 0, NULL, NULL, - purple_smarshal_VOID__STRING_STRING, - G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING); purple_media_signals[CANDIDATES_PREPARED] = g_signal_new("candidates-prepared", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, purple_smarshal_VOID__STRING_STRING, @@ -345,6 +347,11 @@ G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING); + purple_media_signals[LEVEL] = g_signal_new("level", G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + purple_smarshal_VOID__STRING_STRING_DOUBLE, + G_TYPE_NONE, 3, G_TYPE_STRING, + G_TYPE_STRING, G_TYPE_DOUBLE); purple_media_signals[NEW_CANDIDATE] = g_signal_new("new-candidate", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, purple_smarshal_VOID__POINTER_POINTER_OBJECT, @@ -1899,11 +1906,31 @@ gst_element_add_pad(session->media->priv->confbin, ghost); } + gst_element_set_state(session->tee, GST_STATE_PLAYING); gst_element_link(session->src, session->media->priv->confbin); - gst_element_set_state(session->tee, GST_STATE_PLAYING); g_object_get(session->session, "sink-pad", &sinkpad, NULL); - srcpad = gst_element_get_request_pad(session->tee, "src%d"); + if (session->type & PURPLE_MEDIA_SEND_AUDIO) { + gchar *name = g_strdup_printf("volume_%s", session->id); + GstElement *level; + GstElement *volume = gst_element_factory_make("volume", name); + double input_volume = purple_prefs_get_int( + "/purple/media/audio/volume/input")/10.0; + g_free(name); + name = g_strdup_printf("sendlevel_%s", session->id); + level = gst_element_factory_make("level", name); + g_free(name); + gst_bin_add(GST_BIN(session->media->priv->confbin), volume); + gst_bin_add(GST_BIN(session->media->priv->confbin), level); + gst_element_set_state(level, GST_STATE_PLAYING); + gst_element_set_state(volume, GST_STATE_PLAYING); + gst_element_link(volume, level); + gst_element_link(session->tee, volume); + srcpad = gst_element_get_static_pad(level, "src"); + g_object_set(volume, "volume", input_volume, NULL); + } else { + srcpad = gst_element_get_request_pad(session->tee, "src%d"); + } purple_debug_info("media", "connecting pad: %s\n", gst_pad_link(srcpad, sinkpad) == GST_PAD_LINK_OK ? "success" : "failure"); @@ -1960,6 +1987,55 @@ { switch(GST_MESSAGE_TYPE(msg)) { case GST_MESSAGE_ELEMENT: { + if (g_signal_has_handler_pending(media, + purple_media_signals[LEVEL], 0, FALSE) + && gst_structure_has_name( + gst_message_get_structure(msg), "level")) { + GstElement *src = GST_ELEMENT(GST_MESSAGE_SRC(msg)); + gchar *name; + gchar *participant = NULL; + PurpleMediaSession *session = NULL; + gdouble rms_db; + gdouble percent; + const GValue *list; + const GValue *value; + + if (!PURPLE_IS_MEDIA(media) || + GST_ELEMENT_PARENT(src) != + media->priv->confbin) + break; + + name = gst_element_get_name(src); + if (!strncmp(name, "sendlevel_", 10)) { + session = purple_media_get_session( + media, name+10); + } else { + GList *iter = media->priv->streams; + for (; iter; iter = g_list_next(iter)) { + PurpleMediaStream *stream = iter->data; + if (stream->level == src) { + session = stream->session; + participant = stream->participant; + break; + } + } + } + g_free(name); + if (!session) + break; + + list = gst_structure_get_value( + gst_message_get_structure(msg), "rms"); + value = gst_value_list_get_value(list, 0); + rms_db = g_value_get_double(value); + percent = pow(10, rms_db / 20) * 5; + if(percent > 1.0) + percent = 1.0; + + g_signal_emit(media, purple_media_signals[LEVEL], + 0, session->id, participant, percent); + break; + } if (!FS_IS_CONFERENCE(GST_MESSAGE_SRC(msg)) || !PURPLE_IS_MEDIA(media) || media->priv->conference != @@ -2155,9 +2231,6 @@ stream->session->type), NULL); stream->accepted = TRUE; } - - g_signal_emit(media, purple_media_signals[ACCEPTED], - 0, NULL, NULL); } else if (local == TRUE && (type == PURPLE_MEDIA_INFO_MUTE || type == PURPLE_MEDIA_INFO_UNMUTE)) { GList *sessions; @@ -2180,12 +2253,30 @@ sessions, sessions)) { PurpleMediaSession *session = sessions->data; if (session->type & PURPLE_MEDIA_SEND_AUDIO) { + gchar *name = g_strdup_printf("volume_%s", + session->id); GstElement *volume = gst_bin_get_by_name( - GST_BIN(session->src), - "purpleaudioinputvolume"); + GST_BIN(session->media-> + priv->confbin), name); + g_free(name); g_object_set(volume, "mute", active, NULL); } } + } else if (local == TRUE && (type == PURPLE_MEDIA_INFO_PAUSE || + type == PURPLE_MEDIA_INFO_UNPAUSE)) { + gboolean active = (type == PURPLE_MEDIA_INFO_PAUSE); + GList *streams = purple_media_get_streams(media, + session_id, participant); + for (; streams; streams = g_list_delete_link(streams, streams)) { + PurpleMediaStream *stream = streams->data; + if (stream->session->type & PURPLE_MEDIA_SEND_VIDEO) { + g_object_set(stream->stream, "direction", + purple_media_to_fs_stream_direction( + stream->session->type & ((active) ? + ~PURPLE_MEDIA_SEND_VIDEO : + PURPLE_MEDIA_VIDEO)), NULL); + } + } } g_signal_emit(media, purple_media_signals[STREAM_INFO], @@ -2346,11 +2437,21 @@ GstElement *sink = NULL; if (codec->media_type == FS_MEDIA_TYPE_AUDIO) { + GstElement *queue = NULL; + double output_volume = purple_prefs_get_int( + "/purple/media/audio/volume/output")/10.0; /* * Should this instead be: * audioconvert ! audioresample ! liveadder ! * audioresample ! audioconvert ! realsink */ + queue = gst_element_factory_make("queue", NULL); + stream->volume = gst_element_factory_make( + "volume", NULL); + g_object_set(stream->volume, "volume", + output_volume, NULL); + stream->level = gst_element_factory_make( + "level", NULL); stream->src = gst_element_factory_make( "liveadder", NULL); sink = purple_media_manager_get_element(priv->manager, @@ -2358,19 +2459,32 @@ stream->session->media, stream->session->id, stream->participant); + gst_bin_add(GST_BIN(priv->confbin), queue); + gst_bin_add(GST_BIN(priv->confbin), stream->volume); + gst_bin_add(GST_BIN(priv->confbin), stream->level); + gst_bin_add(GST_BIN(priv->confbin), sink); + gst_element_set_state(sink, GST_STATE_PLAYING); + gst_element_set_state(stream->level, GST_STATE_PLAYING); + gst_element_set_state(stream->volume, GST_STATE_PLAYING); + gst_element_set_state(queue, GST_STATE_PLAYING); + gst_element_link(stream->level, sink); + gst_element_link(stream->volume, stream->level); + gst_element_link(queue, stream->volume); + sink = queue; } else if (codec->media_type == FS_MEDIA_TYPE_VIDEO) { stream->src = gst_element_factory_make( "fsfunnel", NULL); sink = gst_element_factory_make( "fakesink", NULL); g_object_set(G_OBJECT(sink), "async", FALSE, NULL); + gst_bin_add(GST_BIN(priv->confbin), sink); + gst_element_set_state(sink, GST_STATE_PLAYING); } stream->tee = gst_element_factory_make("tee", NULL); gst_bin_add_many(GST_BIN(priv->confbin), - stream->src, stream->tee, sink, NULL); - gst_element_sync_state_with_parent(sink); - gst_element_sync_state_with_parent(stream->tee); - gst_element_sync_state_with_parent(stream->src); + stream->src, stream->tee, NULL); + gst_element_set_state(stream->tee, GST_STATE_PLAYING); + gst_element_set_state(stream->src, GST_STATE_PLAYING); gst_element_link_many(stream->src, stream->tee, sink, NULL); } @@ -2504,26 +2618,27 @@ 0, PURPLE_MEDIA_STATE_NEW, session->id, NULL); - session_type = purple_media_from_fs(media_type, - FS_DIRECTION_SEND); - src = purple_media_manager_get_element( - media->priv->manager, session_type, - media, session->id, who); - if (!GST_IS_ELEMENT(src)) { - purple_debug_error("media", - "Error creating src for session %s\n", - session->id); - purple_media_end(media, session->id, NULL); - return FALSE; + if (type_direction & FS_DIRECTION_SEND) { + session_type = purple_media_from_fs(media_type, + FS_DIRECTION_SEND); + src = purple_media_manager_get_element( + media->priv->manager, session_type, + media, session->id, who); + if (!GST_IS_ELEMENT(src)) { + purple_debug_error("media", + "Error creating src for session %s\n", + session->id); + purple_media_end(media, session->id, NULL); + return FALSE; + } + + purple_media_set_src(media, session->id, src); + gst_element_set_state(session->src, GST_STATE_PLAYING); + purple_media_manager_create_output_window( + media->priv->manager, + session->media, + session->id, NULL); } - - purple_media_set_src(media, session->id, src); - gst_element_set_state(session->src, GST_STATE_PLAYING); - - purple_media_manager_create_output_window( - media->priv->manager, - session->media, - session->id, NULL); } if (!(participant = purple_media_add_participant(media, who))) { @@ -2889,14 +3004,23 @@ if (session == NULL) return FALSE; - - g_object_get(session->session, "codecs-ready", &ret, NULL); + if (session->type & (PURPLE_MEDIA_SEND_AUDIO | + PURPLE_MEDIA_SEND_VIDEO)) + g_object_get(session->session, + "codecs-ready", &ret, NULL); + else + ret = TRUE; } else { GList *values = g_hash_table_get_values(media->priv->sessions); for (; values; values = g_list_delete_link(values, values)) { PurpleMediaSession *session = values->data; - g_object_get(session->session, - "codecs-ready", &ret, NULL); + if (session->type & (PURPLE_MEDIA_SEND_AUDIO | + PURPLE_MEDIA_SEND_VIDEO)) + g_object_get(session->session, + "codecs-ready", &ret, NULL); + else + ret = TRUE; + if (ret == FALSE) break; } @@ -2983,6 +3107,8 @@ g_return_if_fail(PURPLE_IS_MEDIA(media)); + purple_prefs_set_int("/purple/media/audio/volume/input", level); + if (session_id == NULL) sessions = g_hash_table_get_values(media->priv->sessions); else @@ -2993,10 +3119,13 @@ PurpleMediaSession *session = sessions->data; if (session->type & PURPLE_MEDIA_SEND_AUDIO) { + gchar *name = g_strdup_printf("volume_%s", + session->id); GstElement *volume = gst_bin_get_by_name( - GST_BIN(session->src), - "purpleaudioinputvolume"); - g_object_set(volume, "volume", level, NULL); + GST_BIN(session->media->priv->confbin), + name); + g_free(name); + g_object_set(volume, "volume", level/10.0, NULL); } } #endif @@ -3011,34 +3140,17 @@ g_return_if_fail(PURPLE_IS_MEDIA(media)); + purple_prefs_set_int("/purple/media/audio/volume/output", level); + streams = purple_media_get_streams(media, session_id, participant); for (; streams; streams = g_list_delete_link(streams, streams)) { PurpleMediaStream *stream = streams->data; - if (stream->session->type & PURPLE_MEDIA_RECV_AUDIO) { - GstElement *tee = stream->tee; - GstIterator *iter = gst_element_iterate_src_pads(tee); - GstPad *sinkpad; - while (gst_iterator_next(iter, (gpointer)&sinkpad) - == GST_ITERATOR_OK) { - GstPad *peer = gst_pad_get_peer(sinkpad); - GstElement *volume; - - if (peer == NULL) { - gst_object_unref(sinkpad); - continue; - } - - volume = gst_bin_get_by_name(GST_BIN( - GST_OBJECT_PARENT(peer)), - "purpleaudiooutputvolume"); - g_object_set(volume, "volume", level, NULL); - gst_object_unref(peer); - gst_object_unref(sinkpad); - } - gst_iterator_free(iter); + if (stream->session->type & PURPLE_MEDIA_RECV_AUDIO + && GST_IS_ELEMENT(stream->volume)) { + g_object_set(stream->volume, "volume", level/10.0, NULL); } } #endif
--- a/libpurple/media.h Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/media.h Thu Aug 13 17:15:06 2009 +0000 @@ -103,6 +103,8 @@ PURPLE_MEDIA_INFO_REJECT, PURPLE_MEDIA_INFO_MUTE, PURPLE_MEDIA_INFO_UNMUTE, + PURPLE_MEDIA_INFO_PAUSE, + PURPLE_MEDIA_INFO_UNPAUSE, PURPLE_MEDIA_INFO_HOLD, PURPLE_MEDIA_INFO_UNHOLD, } PurpleMediaInfoType;
--- a/libpurple/mediamanager.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/mediamanager.c Thu Aug 13 17:15:06 2009 +0000 @@ -39,6 +39,7 @@ #ifdef USE_VV #include <gst/farsight/fs-conference-iface.h> +#include <gst/farsight/fs-element-added-notifier.h> #include <gst/interfaces/xoverlay.h> /** @copydoc _PurpleMediaManagerPrivate */ @@ -156,6 +157,12 @@ media->priv = PURPLE_MEDIA_MANAGER_GET_PRIVATE(media); media->priv->medias = NULL; media->priv->next_output_window_id = 1; + + purple_prefs_add_none("/purple/media"); + purple_prefs_add_none("/purple/media/audio"); + purple_prefs_add_none("/purple/media/audio/volume"); + purple_prefs_add_int("/purple/media/audio/volume/input", 10); + purple_prefs_add_int("/purple/media/audio/volume/output", 10); } static void @@ -229,6 +236,10 @@ g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), NULL); if (manager->priv->pipeline == NULL) { + FsElementAddedNotifier *notifier; + gchar *filename; + GError *err = NULL; + GKeyFile *keyfile; GstBus *bus; manager->priv->pipeline = gst_pipeline_new(NULL); @@ -241,6 +252,38 @@ gst_bus_sync_signal_handler, NULL); gst_object_unref(bus); + filename = g_build_filename(purple_user_dir(), + "fs-element.conf", NULL); + keyfile = g_key_file_new(); + if (!g_key_file_load_from_file(keyfile, filename, + G_KEY_FILE_NONE, &err)) { + if (err->code == 4) + purple_debug_info("mediamanager", + "Couldn't read " + "fs-element.conf: %s\n", + err->message); + else + purple_debug_error("mediamanager", + "Error reading " + "fs-element.conf: %s\n", + err->message); + g_error_free(err); + } + g_free(filename); + + /* Hack to make alsasrc stop messing up audio timestamps */ + if (!g_key_file_has_key(keyfile, + "alsasrc", "slave-method", NULL)) { + g_key_file_set_integer(keyfile, + "alsasrc", "slave-method", 2); + } + + notifier = fs_element_added_notifier_new(); + fs_element_added_notifier_add(notifier, + GST_BIN(manager->priv->pipeline)); + fs_element_added_notifier_set_properties_from_keyfile( + notifier, keyfile); + gst_element_set_state(manager->priv->pipeline, GST_STATE_PLAYING); } @@ -356,15 +399,19 @@ GstElement *parent = GST_ELEMENT_PARENT(pad); GstIterator *iter; GstPad *remaining_pad; + GstIteratorResult result; gst_element_release_request_pad(GST_ELEMENT_PARENT(pad), pad); - iter = gst_element_iterate_pads(parent); + iter = gst_element_iterate_src_pads(parent); - if (gst_iterator_next(iter, (gpointer)&remaining_pad) - == GST_ITERATOR_DONE) { + result = gst_iterator_next(iter, (gpointer)&remaining_pad); + + if (result == GST_ITERATOR_DONE) { gst_element_set_locked_state(parent, TRUE); gst_element_set_state(parent, GST_STATE_NULL); gst_bin_remove(GST_BIN(GST_ELEMENT_PARENT(parent)), parent); + } else if (result == GST_ITERATOR_OK) { + gst_object_unref(remaining_pad); } gst_iterator_free(iter); @@ -426,7 +473,6 @@ gst_element_link(tee, fakesink); ret = bin; - gst_element_set_locked_state(ret, TRUE); gst_object_ref(ret); gst_bin_add(GST_BIN(purple_media_manager_get_pipeline( manager)), ret); @@ -667,7 +713,7 @@ (participant == ow->participant)) && !strcmp(session_id, ow->session_id)) { GstBus *bus; - GstElement *queue; + GstElement *queue, *colorspace; GstElement *tee = purple_media_get_tee(media, session_id, participant); @@ -676,6 +722,8 @@ queue = gst_element_factory_make( "queue", NULL); + colorspace = gst_element_factory_make( + "ffmpegcolorspace", NULL); ow->sink = purple_media_manager_get_element( manager, PURPLE_MEDIA_RECV_VIDEO, ow->media, ow->session_id, @@ -696,7 +744,7 @@ } gst_bin_add_many(GST_BIN(GST_ELEMENT_PARENT(tee)), - queue, ow->sink, NULL); + queue, colorspace, ow->sink, NULL); bus = gst_pipeline_get_bus(GST_PIPELINE( manager->priv->pipeline)); @@ -704,9 +752,11 @@ G_CALLBACK(window_id_cb), ow); gst_object_unref(bus); - gst_element_sync_state_with_parent(ow->sink); - gst_element_link(queue, ow->sink); - gst_element_sync_state_with_parent(queue); + gst_element_set_state(ow->sink, GST_STATE_PLAYING); + gst_element_set_state(colorspace, GST_STATE_PLAYING); + gst_element_set_state(queue, GST_STATE_PLAYING); + gst_element_link(colorspace, ow->sink); + gst_element_link(queue, colorspace); gst_element_link(tee, queue); } } @@ -775,8 +825,14 @@ GstPad *pad = gst_element_get_static_pad( output_window->sink, "sink"); GstPad *peer = gst_pad_get_peer(pad); - GstElement *queue = GST_ELEMENT_PARENT(peer); + GstElement *colorspace = GST_ELEMENT_PARENT(peer), *queue; gst_object_unref(pad); + gst_object_unref(peer); + pad = gst_element_get_static_pad(colorspace, "sink"); + peer = gst_pad_get_peer(pad); + queue = GST_ELEMENT_PARENT(peer); + gst_object_unref(pad); + gst_object_unref(peer); pad = gst_element_get_static_pad(queue, "sink"); peer = gst_pad_get_peer(pad); gst_object_unref(pad); @@ -785,6 +841,9 @@ gst_element_set_locked_state(queue, TRUE); gst_element_set_state(queue, GST_STATE_NULL); gst_bin_remove(GST_BIN(GST_ELEMENT_PARENT(queue)), queue); + gst_element_set_locked_state(colorspace, TRUE); + gst_element_set_state(colorspace, GST_STATE_NULL); + gst_bin_remove(GST_BIN(GST_ELEMENT_PARENT(colorspace)), colorspace); gst_element_set_locked_state(output_window->sink, TRUE); gst_element_set_state(output_window->sink, GST_STATE_NULL); gst_bin_remove(GST_BIN(GST_ELEMENT_PARENT(output_window->sink)),
--- a/libpurple/pounce.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/pounce.c Thu Aug 13 17:15:06 2009 +0000 @@ -1157,4 +1157,7 @@ } purple_signals_disconnect_by_handle(purple_pounces_get_handle()); + + g_hash_table_destroy(pounce_handlers); + pounce_handlers = NULL; }
--- a/libpurple/prefs.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/prefs.c Thu Aug 13 17:15:06 2009 +0000 @@ -1450,6 +1450,8 @@ sync_prefs(); } + purple_prefs_disconnect_by_handle(purple_prefs_get_handle()); + prefs_loaded = FALSE; purple_prefs_destroy(); g_hash_table_destroy(prefs_hash);
--- a/libpurple/protocols/bonjour/mdns_avahi.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/bonjour/mdns_avahi.c Thu Aug 13 17:15:06 2009 +0000 @@ -200,8 +200,8 @@ } if (!bonjour_buddy_check(bb)) { + b_impl->resolvers = g_slist_remove(b_impl->resolvers, rd); _cleanup_resolver_data(rd); - b_impl->resolvers = g_slist_remove(b_impl->resolvers, rd); /* If this was the last resolver, remove the buddy */ if (b_impl->resolvers == NULL) { if (pb != NULL)
--- a/libpurple/protocols/gg/lib/libgadu.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/gg/lib/libgadu.c Thu Aug 13 17:15:06 2009 +0000 @@ -790,6 +790,7 @@ gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() body recv(%d,%p,%d) = %d\n", sess->fd, buf + sizeof(h) + offset, size, ret); if (!ret) { gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() body recv() failed: connection broken\n"); + free(buf); errno = ECONNRESET; return NULL; }
--- a/libpurple/protocols/irc/irc.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/irc/irc.c Thu Aug 13 17:15:06 2009 +0000 @@ -569,9 +569,20 @@ static void irc_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group) { struct irc_conn *irc = (struct irc_conn *)gc->proto_data; - struct irc_buddy *ib = g_new0(struct irc_buddy, 1); - ib->name = g_strdup(purple_buddy_get_name(buddy)); - g_hash_table_replace(irc->buddies, ib->name, ib); + struct irc_buddy *ib; + const char *bname = purple_buddy_get_name(buddy); + + ib = g_hash_table_lookup(irc->buddies, bname); + if (ib != NULL) { + ib->ref++; + purple_prpl_got_user_status(irc->account, bname, + ib->online ? "available" : "offline", NULL); + } else { + ib = g_new0(struct irc_buddy, 1); + ib->name = g_strdup(bname); + ib->ref = 1; + g_hash_table_replace(irc->buddies, ib->name, ib); + } /* if the timer isn't set, this is during signon, so we don't want to flood * ourself off with ISON's, so we don't, but after that we want to know when @@ -583,7 +594,12 @@ static void irc_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group) { struct irc_conn *irc = (struct irc_conn *)gc->proto_data; - g_hash_table_remove(irc->buddies, purple_buddy_get_name(buddy)); + struct irc_buddy *ib; + + ib = g_hash_table_lookup(irc->buddies, purple_buddy_get_name(buddy)); + if (ib && --ib->ref == 0) { + g_hash_table_remove(irc->buddies, purple_buddy_get_name(buddy)); + } } static void read_input(struct irc_conn *irc, int len)
--- a/libpurple/protocols/irc/irc.h Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/irc/irc.h Thu Aug 13 17:15:06 2009 +0000 @@ -97,6 +97,7 @@ char *name; gboolean online; gboolean flag; + int ref; }; typedef int (*IRCCmdCallback) (struct irc_conn *irc, const char *cmd, const char *target, const char **args);
--- a/libpurple/protocols/irc/msgs.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/irc/msgs.c Thu Aug 13 17:15:06 2009 +0000 @@ -103,7 +103,8 @@ PurpleBuddy *b = buddies->data; struct irc_buddy *ib = g_new0(struct irc_buddy, 1); ib->name = g_strdup(purple_buddy_get_name(b)); - g_hash_table_insert(irc->buddies, ib->name, ib); + ib->ref = 1; + g_hash_table_replace(irc->buddies, ib->name, ib); } irc_blist_timeout(irc);
--- a/libpurple/protocols/jabber/bosh.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/jabber/bosh.c Thu Aug 13 17:15:06 2009 +0000 @@ -29,8 +29,12 @@ #include "bosh.h" -#define MAX_HTTP_CONNECTIONS 2 +/* The number of HTTP connections to use. This MUST be at least 2. */ +#define NUM_HTTP_CONNECTIONS 2 +/* How many failed connection attempts before it becomes a fatal error */ #define MAX_FAILED_CONNECTIONS 3 +/* How long in seconds to queue up outgoing messages */ +#define BUFFER_SEND_IN_SECS 1 typedef struct _PurpleHTTPConnection PurpleHTTPConnection; @@ -40,55 +44,63 @@ static char *bosh_useragent = NULL; typedef enum { + PACKET_NORMAL, PACKET_TERMINATE, - PACKET_STREAM_RESTART, - PACKET_NORMAL, + PACKET_FLUSH, } PurpleBOSHPacketType; struct _PurpleBOSHConnection { JabberStream *js; + PurpleHTTPConnection *connections[NUM_HTTP_CONNECTIONS]; + + PurpleCircBuffer *pending; + PurpleBOSHConnectionConnectFunction connect_cb; + PurpleBOSHConnectionReceiveFunction receive_cb; + + /* Must be big enough to hold 2^53 - 1 */ + char *sid; + guint64 rid; + + /* decoded URL */ + char *host; + char *path; + guint16 port; + gboolean pipelining; - PurpleHTTPConnection *connections[MAX_HTTP_CONNECTIONS]; - unsigned short failed_connections; + gboolean ssl; + gboolean needs_restart; enum { BOSH_CONN_OFFLINE, BOSH_CONN_BOOTING, BOSH_CONN_ONLINE } state; - gboolean ssl; - gboolean needs_restart; + guint8 failed_connections; - /* decoded URL */ - char *host; - int port; - char *path; - - /* Must be big enough to hold 2^53 - 1 */ - guint64 rid; - char *sid; - - unsigned int inactivity_timer; int max_inactivity; int wait; - PurpleCircBuffer *pending; int max_requests; int requests; - PurpleBOSHConnectionConnectFunction connect_cb; - PurpleBOSHConnectionReceiveFunction receive_cb; + guint inactivity_timer; + guint send_timer; }; struct _PurpleHTTPConnection { PurpleBOSHConnection *bosh; PurpleSslConnection *psc; + + PurpleCircBuffer *write_buf; + GString *read_buf; + + gsize handled_len; + gsize body_len; + int fd; guint readh; guint writeh; - PurpleCircBuffer *write_buffer; - enum { HTTP_CONN_OFFLINE, HTTP_CONN_CONNECTING, @@ -96,16 +108,14 @@ } state; int requests; /* number of outstanding HTTP requests */ - GString *buf; gboolean headers_done; - gsize handled_len; - gsize body_len; }; static void http_connection_connect(PurpleHTTPConnection *conn); static void http_connection_send_request(PurpleHTTPConnection *conn, const GString *req); +static gboolean send_timer_cb(gpointer data); void jabber_bosh_init(void) { @@ -140,7 +150,7 @@ conn->fd = -1; conn->state = HTTP_CONN_OFFLINE; - conn->write_buffer = purple_circ_buffer_new(0 /* default grow size */); + conn->write_buf = purple_circ_buffer_new(0 /* default grow size */); return conn; } @@ -148,11 +158,11 @@ static void jabber_bosh_http_connection_destroy(PurpleHTTPConnection *conn) { - if (conn->buf) - g_string_free(conn->buf, TRUE); + if (conn->read_buf) + g_string_free(conn->read_buf, TRUE); - if (conn->write_buffer) - purple_circ_buffer_destroy(conn->write_buffer); + if (conn->write_buf) + purple_circ_buffer_destroy(conn->write_buf); if (conn->readh) purple_input_remove(conn->readh); if (conn->writeh) @@ -227,12 +237,14 @@ g_free(conn->host); g_free(conn->path); + if (conn->send_timer) + purple_timeout_remove(conn->send_timer); if (conn->inactivity_timer) purple_timeout_remove(conn->inactivity_timer); purple_circ_buffer_destroy(conn->pending); - for (i = 0; i < MAX_HTTP_CONNECTIONS; ++i) { + for (i = 0; i < NUM_HTTP_CONNECTIONS; ++i) { if (conn->connections[i]) jabber_bosh_http_connection_destroy(conn->connections[i]); } @@ -250,6 +262,19 @@ { int i; + if (purple_debug_is_verbose()) { + for (i = 0; i < NUM_HTTP_CONNECTIONS; ++i) { + PurpleHTTPConnection *httpconn = conn->connections[i]; + if (httpconn == NULL) + purple_debug_misc("jabber", "BOSH %p->connections[%d] = (nil)\n", + conn, i); + else + purple_debug_misc("jabber", "BOSH %p->connections[%d] = %p, state = %d" + ", requests = %d\n", conn, i, httpconn, + httpconn->state, httpconn->requests); + } + } + /* Easy solution: Does everyone involved support pipelining? Hooray! Just use * one TCP connection! */ if (conn->pipelining) @@ -257,7 +282,7 @@ conn->connections[0] : NULL; /* First loop, look for a connection that's ready */ - for (i = 0; i < MAX_HTTP_CONNECTIONS; ++i) { + for (i = 0; i < NUM_HTTP_CONNECTIONS; ++i) { if (conn->connections[i] && conn->connections[i]->state == HTTP_CONN_CONNECTED && conn->connections[i]->requests == 0) @@ -265,14 +290,14 @@ } /* Second loop, is something currently connecting? If so, just queue up. */ - for (i = 0; i < MAX_HTTP_CONNECTIONS; ++i) { + for (i = 0; i < NUM_HTTP_CONNECTIONS; ++i) { if (conn->connections[i] && conn->connections[i]->state == HTTP_CONN_CONNECTING) return NULL; } /* Third loop, look for one that's NULL and create a new connection */ - for (i = 0; i < MAX_HTTP_CONNECTIONS; ++i) { + for (i = 0; i < NUM_HTTP_CONNECTIONS; ++i) { if (!conn->connections[i]) { purple_debug_info("jabber", "bosh: Creating and connecting new httpconn\n"); conn->connections[i] = jabber_bosh_http_connection_init(conn); @@ -282,6 +307,8 @@ } } + purple_debug_warning("jabber", "Could not find a HTTP connection!\n"); + /* None available. */ return NULL; } @@ -293,9 +320,29 @@ PurpleHTTPConnection *chosen; GString *packet = NULL; + if (type != PACKET_FLUSH && type != PACKET_TERMINATE) { + /* + * Unless this is a flush (or session terminate, which needs to be + * sent immediately), queue up the data and start a timer to flush + * the buffer. + */ + if (data) { + int len = data ? strlen(data) : 0; + purple_circ_buffer_append(conn->pending, data, len); + } + + if (purple_debug_is_verbose()) + purple_debug_misc("jabber", "bosh: %p has %" G_GSIZE_FORMAT " bytes in " + "the buffer.\n", conn, conn->pending->bufused); + if (conn->send_timer == 0) + conn->send_timer = purple_timeout_add_seconds(BUFFER_SEND_IN_SECS, + send_timer_cb, conn); + return; + } + chosen = find_available_http_connection(conn); - if (type != PACKET_NORMAL && !chosen) { + if (!chosen) { /* * For non-ordinary traffic, we can't 'buffer' it, so use the * first connection. @@ -303,25 +350,16 @@ chosen = conn->connections[0]; if (chosen->state != HTTP_CONN_CONNECTED) { - purple_debug_info("jabber", "Unable to find a ready BOSH " + purple_debug_warning("jabber", "Unable to find a ready BOSH " "connection. Ignoring send of type 0x%02x.\n", type); return; } } - if (type == PACKET_NORMAL && (!chosen || - (conn->max_requests > 0 && conn->requests == conn->max_requests))) { - /* - * For normal data, send up to max_requests requests at a time or there is no - * connection ready (likely, we're currently opening a second connection and - * will send these packets when connected). - */ - if (data) { - int len = data ? strlen(data) : 0; - purple_circ_buffer_append(conn->pending, data, len); - } - - return; + /* We're flushing the send buffer, so remove the send timer */ + if (conn->send_timer != 0) { + purple_timeout_remove(conn->send_timer); + conn->send_timer = 0; } packet = g_string_new(NULL); @@ -337,7 +375,7 @@ conn->sid, conn->js->user->domain); - if (type == PACKET_STREAM_RESTART) { + if (conn->needs_restart) { packet = g_string_append(packet, " xmpp:restart='true'/>"); /* TODO: Do we need to wait for a response? */ conn->needs_restart = FALSE; @@ -369,7 +407,7 @@ static void jabber_bosh_connection_stream_restart(PurpleBOSHConnection *conn) { conn->needs_restart = TRUE; - jabber_bosh_connection_send(conn, PACKET_STREAM_RESTART, NULL); + jabber_bosh_connection_send(conn, PACKET_NORMAL, NULL); } static gboolean jabber_bosh_connection_error_check(PurpleBOSHConnection *conn, xmlnode *node) { @@ -388,12 +426,46 @@ } static gboolean +send_timer_cb(gpointer data) +{ + PurpleBOSHConnection *bosh; + + bosh = data; + bosh->send_timer = 0; + + jabber_bosh_connection_send(bosh, PACKET_FLUSH, NULL); + + return FALSE; +} + +static gboolean bosh_inactivity_cb(gpointer data) { PurpleBOSHConnection *bosh = data; + bosh->inactivity_timer = 0; - jabber_bosh_connection_send(bosh, PACKET_NORMAL, NULL); - return TRUE; + if (bosh->send_timer != 0) + purple_timeout_remove(bosh->send_timer); + + /* clears bosh->send_timer */ + send_timer_cb(bosh); + + return FALSE; +} + +static void +restart_inactivity_timer(PurpleBOSHConnection *conn) +{ + if (conn->inactivity_timer != 0) { + purple_timeout_remove(conn->inactivity_timer); + conn->inactivity_timer = 0; + } + + if (conn->max_inactivity != 0) { + conn->inactivity_timer = + purple_timeout_add_seconds(conn->max_inactivity - 5 /* rounding */, + bosh_inactivity_cb, conn); + } } static void jabber_bosh_connection_received(PurpleBOSHConnection *conn, xmlnode *node) { @@ -490,18 +562,18 @@ if (inactivity) { conn->max_inactivity = atoi(inactivity); - if (conn->max_inactivity <= 2) { + if (conn->max_inactivity <= 5) { purple_debug_warning("jabber", "Ignoring bogusly small inactivity: %s\n", inactivity); conn->max_inactivity = 0; } else { /* TODO: Integrate this with jabber.c keepalive checks... */ + /* TODO: Can this check fail? It shouldn't */ if (conn->inactivity_timer == 0) { - purple_debug_misc("jabber", "Starting BOSH inactivity timer for %d secs (compensating for rounding)\n", + purple_debug_misc("jabber", "Starting BOSH inactivity timer " + "for %d secs (compensating for rounding)\n", conn->max_inactivity - 5); - conn->inactivity_timer = purple_timeout_add_seconds( - conn->max_inactivity - 5 /* rounding */, - bosh_inactivity_cb, conn); + restart_inactivity_timer(conn); } } } @@ -512,7 +584,6 @@ /* FIXME: Depending on receiving features might break with some hosts */ packet = xmlnode_get_child(node, "features"); conn->state = BOSH_CONN_ONLINE; - conn->js->use_bosh = TRUE; conn->receive_cb = auth_response_cb; jabber_stream_features_parse(conn->js, packet); } @@ -578,10 +649,14 @@ { /* Indicate we're ready and reset some variables */ conn->state = HTTP_CONN_CONNECTED; + if (conn->requests != 0) + purple_debug_error("jabber", "bosh: httpconn %p has %d requests, != 0\n", + conn, conn->requests); + conn->requests = 0; - if (conn->buf) { - g_string_free(conn->buf, TRUE); - conn->buf = NULL; + if (conn->read_buf) { + g_string_free(conn->read_buf, TRUE); + conn->read_buf = NULL; } conn->headers_done = FALSE; conn->handled_len = conn->body_len = 0; @@ -592,22 +667,12 @@ purple_debug_info("jabber", "BOSH session already exists. Trying to reuse it.\n"); if (conn->bosh->requests == 0 || conn->bosh->pending->bufused > 0) { /* Send the pending data */ - jabber_bosh_connection_send(conn->bosh, PACKET_NORMAL, NULL); + jabber_bosh_connection_send(conn->bosh, PACKET_FLUSH, NULL); } -#if 0 - conn->bosh->receive_cb = jabber_bosh_connection_received; - if (conn->bosh->connect_cb) - conn->bosh->connect_cb(conn->bosh); -#endif } else jabber_bosh_connection_boot(conn->bosh); } -void jabber_bosh_connection_refresh(PurpleBOSHConnection *conn) -{ - jabber_bosh_connection_send(conn, PACKET_NORMAL, NULL); -} - static void http_connection_disconnected(PurpleHTTPConnection *conn) { /* @@ -633,9 +698,21 @@ conn->writeh = 0; } - if (conn->bosh->pipelining) + if (conn->requests > 0 && conn->read_buf->len == 0) { + purple_debug_error("jabber", "bosh: Adjusting BOSHconn requests (%d) to %d\n", + conn->bosh->requests, conn->bosh->requests - conn->requests); + conn->bosh->requests -= conn->requests; + conn->requests = 0; + } + + if (conn->bosh->pipelining) { /* Hmmmm, fall back to multiple connections */ conn->bosh->pipelining = FALSE; + if (conn->bosh->connections[1] == NULL) { + conn->bosh->connections[1] = jabber_bosh_http_connection_init(conn->bosh); + http_connection_connect(conn->bosh->connections[1]); + } + } if (++conn->bosh->failed_connections == MAX_FAILED_CONNECTIONS) { purple_connection_error_reason(conn->bosh->js->gc, @@ -661,7 +738,7 @@ { const char *cursor; - cursor = conn->buf->str + conn->handled_len; + cursor = conn->read_buf->str + conn->handled_len; if (!conn->headers_done) { const char *content_length = purple_strcasestr(cursor, "\r\nContent-Length"); @@ -690,26 +767,26 @@ if (end_of_headers) { conn->headers_done = TRUE; - conn->handled_len = end_of_headers - conn->buf->str + 4; + conn->handled_len = end_of_headers - conn->read_buf->str + 4; cursor = end_of_headers + 4; } else { - conn->handled_len = conn->buf->len; + conn->handled_len = conn->read_buf->len; return; } } /* Have we handled everything in the buffer? */ - if (conn->handled_len >= conn->buf->len) + if (conn->handled_len >= conn->read_buf->len) return; /* Have we read all that the Content-Length promised us? */ - if (conn->buf->len - conn->handled_len < conn->body_len) + if (conn->read_buf->len - conn->handled_len < conn->body_len) return; --conn->requests; --conn->bosh->requests; - http_received_cb(conn->buf->str + conn->handled_len, conn->body_len, + http_received_cb(conn->read_buf->str + conn->handled_len, conn->body_len, conn->bosh); if (conn->bosh->state == BOSH_CONN_ONLINE && @@ -718,8 +795,8 @@ jabber_bosh_connection_send(conn->bosh, PACKET_NORMAL, NULL); } - g_string_free(conn->buf, TRUE); - conn->buf = NULL; + g_string_free(conn->read_buf, TRUE); + conn->read_buf = NULL; conn->headers_done = FALSE; conn->handled_len = conn->body_len = 0; } @@ -734,8 +811,8 @@ char buffer[1025]; int cnt, count = 0; - if (!conn->buf) - conn->buf = g_string_new(NULL); + if (!conn->read_buf) + conn->read_buf = g_string_new(NULL); do { if (conn->psc) @@ -745,7 +822,7 @@ if (cnt > 0) { count += cnt; - g_string_append_len(conn->buf, buffer, cnt); + g_string_append_len(conn->read_buf, buffer, cnt); } } while (cnt > 0); @@ -765,7 +842,7 @@ /* Process what we do have */ } - if (conn->buf->len > 0) + if (conn->read_buf->len > 0) jabber_bosh_http_connection_process(conn); } @@ -879,7 +956,7 @@ { PurpleHTTPConnection *conn = data; int ret; - int writelen = purple_circ_buffer_get_max_read(conn->write_buffer); + int writelen = purple_circ_buffer_get_max_read(conn->write_buf); if (writelen == 0) { purple_input_remove(conn->writeh); @@ -887,7 +964,7 @@ return; } - ret = http_connection_do_send(conn, conn->write_buffer->outptr, writelen); + ret = http_connection_do_send(conn, conn->write_buf->outptr, writelen); if (ret < 0 && errno == EAGAIN) return; @@ -906,7 +983,7 @@ return; } - purple_circ_buffer_mark_read(conn->write_buffer, ret); + purple_circ_buffer_mark_read(conn->write_buf, ret); } static void @@ -916,6 +993,9 @@ int ret; size_t len; + /* Sending something to the server, restart the inactivity timer */ + restart_inactivity_timer(conn->bosh); + data = g_strdup_printf("POST %s HTTP/1.1\r\n" "Host: %s\r\n" "User-Agent: %s\r\n" @@ -930,6 +1010,10 @@ ++conn->requests; ++conn->bosh->requests; + if (purple_debug_is_unsafe() && purple_debug_is_verbose()) + /* Will contain passwords for SASL PLAIN and is verbose */ + purple_debug_misc("jabber", "BOSH: Sending %s\n", data); + if (conn->writeh == 0) ret = http_connection_do_send(conn, data, len); else { @@ -956,7 +1040,7 @@ if (conn->writeh == 0) conn->writeh = purple_input_add(conn->psc ? conn->psc->fd : conn->fd, PURPLE_INPUT_WRITE, http_connection_send_cb, conn); - purple_circ_buffer_append(conn->write_buffer, data + ret, len - ret); + purple_circ_buffer_append(conn->write_buf, data + ret, len - ret); } }
--- a/libpurple/protocols/jabber/bosh.h Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/jabber/bosh.h Thu Aug 13 17:15:06 2009 +0000 @@ -37,5 +37,4 @@ void jabber_bosh_connection_connect(PurpleBOSHConnection *conn); void jabber_bosh_connection_close(PurpleBOSHConnection *conn); void jabber_bosh_connection_send_raw(PurpleBOSHConnection *conn, const char *data); -void jabber_bosh_connection_refresh(PurpleBOSHConnection *conn); #endif /* PURPLE_JABBER_BOSH_H_ */
--- a/libpurple/protocols/jabber/buddy.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/jabber/buddy.c Thu Aug 13 17:15:06 2009 +0000 @@ -463,6 +463,11 @@ if(!js->vcard_fetched) return; + if (js->vcard_timer) { + purple_timeout_remove(js->vcard_timer); + js->vcard_timer = 0; + } + g_free(js->avatar_hash); js->avatar_hash = NULL; @@ -508,6 +513,7 @@ xmlnode_insert_data(binval, enc, -1); g_free(enc); + purple_imgstore_unref(img); } else if (vc_node) { xmlnode *photo; /* TODO: Remove all PHOTO children? (see above note) */ @@ -854,12 +860,26 @@ } } +static gboolean +set_own_vcard_cb(gpointer data) +{ + JabberStream *js = data; + PurpleAccount *account = purple_connection_get_account(js->gc); + + js->vcard_timer = 0; + + jabber_set_info(js->gc, purple_account_get_user_info(account)); + + return FALSE; +} + static void jabber_vcard_save_mine(JabberStream *js, const char *from, JabberIqType type, const char *id, xmlnode *packet, gpointer data) { xmlnode *vcard, *photo, *binval; char *txt, *vcard_hash = NULL; + PurpleAccount *account; if (type == JABBER_IQ_ERROR) { xmlnode *error; @@ -870,12 +890,13 @@ return; } + account = purple_connection_get_account(js->gc); + if((vcard = xmlnode_get_child(packet, "vCard")) || (vcard = xmlnode_get_child_with_namespace(packet, "query", "vcard-temp"))) { txt = xmlnode_to_str(vcard, NULL); - purple_account_set_user_info(purple_connection_get_account(js->gc), txt); - + purple_account_set_user_info(account, txt); g_free(txt); } else { /* if we have no vCard, then lets not overwrite what we might have locally */ @@ -898,8 +919,17 @@ /* Republish our vcard if the photo is different than the server's */ if (!purple_strequal(vcard_hash, js->initial_avatar_hash)) { - PurpleAccount *account = purple_connection_get_account(js->gc); - jabber_set_info(js->gc, purple_account_get_user_info(account)); + /* + * Google Talk has developed the behavior that it will not accept + * a vcard set in the first 10 seconds (or so) of the connection; + * it returns an error (namespaces trimmed): + * <error code="500" type="wait"><internal-server-error/></error>. + */ + if (js->googletalk) + js->vcard_timer = purple_timeout_add_seconds(10, set_own_vcard_cb, + js); + else + jabber_set_info(js->gc, purple_account_get_user_info(account)); } else if (js->initial_avatar_hash) { /* Our photo is in the vcard, so advertise vcard-temp updates */ js->avatar_hash = g_strdup(js->initial_avatar_hash); @@ -1018,7 +1048,12 @@ if(!strcmp(child2->name, "POBOX")) { purple_notify_user_info_add_pair(user_info, _("P.O. Box"), text2); - } else if(!strcmp(child2->name, "EXTADR")) { + } else if (g_str_equal(child2->name, "EXTADD") || g_str_equal(child2->name, "EXTADR")) { + /* + * EXTADD is correct, EXTADR is generated by other + * clients. The next time someone reads this, remove + * EXTADR. + */ purple_notify_user_info_add_pair(user_info, _("Extended Address"), text2); } else if(!strcmp(child2->name, "STREET")) { purple_notify_user_info_add_pair(user_info, _("Street Address"), text2); @@ -1159,6 +1194,22 @@ g_free(jbri); } +static guint jbir_hash(gconstpointer v) +{ + if (v) + return g_str_hash(v); + else + return 0; +} + +static gboolean jbir_equal(gconstpointer v1, gconstpointer v2) +{ + const gchar *resource_1 = v1; + const gchar *resource_2 = v2; + + return purple_strequal(resource_1, resource_2); +} + static void jabber_version_parse(JabberStream *js, const char *from, JabberIqType type, const char *id, xmlnode *packet, gpointer data) @@ -1430,9 +1481,7 @@ char *full_jid = NULL; const char *to; - g_return_if_fail(jbr->name != NULL); - - if (is_bare_jid) { + if (is_bare_jid && jbr->name) { full_jid = g_strdup_printf("%s/%s", jid, jbr->name); to = full_jid; } else @@ -1501,7 +1550,7 @@ jbi->jid = g_strdup(jid); jbi->js = js; jbi->jb = jb; - jbi->resources = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, jabber_buddy_info_resource_free); + jbi->resources = g_hash_table_new_full(jbir_hash, jbir_equal, g_free, jabber_buddy_info_resource_free); jbi->user_info = purple_notify_user_info_new(); iq = jabber_iq_new(js, JABBER_IQ_GET); @@ -1731,9 +1780,7 @@ if(!jb) return m; - /* XXX: fix the NOT ME below */ - - if(js->protocol_version == JABBER_PROTO_0_9 /* && NOT ME */) { + if (js->protocol_version == JABBER_PROTO_0_9 && jb != js->user_jb) { if(jb->invisible & JABBER_INVIS_BUDDY) { act = purple_menu_action_new(_("Un-hide From"), PURPLE_CALLBACK(jabber_buddy_make_visible), @@ -1746,7 +1793,7 @@ m = g_list_append(m, act); } - if(jb->subscription & JABBER_SUB_FROM /* && NOT ME */) { + if(jb->subscription & JABBER_SUB_FROM && jb != js->user_jb) { act = purple_menu_action_new(_("Cancel Presence Notification"), PURPLE_CALLBACK(jabber_buddy_cancel_presence_notification), NULL, NULL); @@ -1759,7 +1806,7 @@ NULL, NULL); m = g_list_append(m, act); - } else /* if(NOT ME) */{ + } else if (jb != js->user_jb) { /* shouldn't this just happen automatically when the buddy is removed? */
--- a/libpurple/protocols/jabber/buddy.h Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/jabber/buddy.h Thu Aug 13 17:15:06 2009 +0000 @@ -33,10 +33,12 @@ JABBER_BUDDY_STATE_DND } JabberBuddyState; +typedef struct _JabberBuddy JabberBuddy; + #include "jabber.h" #include "caps.h" -typedef struct _JabberBuddy { +struct _JabberBuddy { GList *resources; char *error_msg; enum { @@ -52,7 +54,7 @@ JABBER_SUB_BOTH = (JABBER_SUB_TO | JABBER_SUB_FROM), JABBER_SUB_REMOVE = 1 << 4 } subscription; -} JabberBuddy; +}; typedef struct _JabberAdHocCommands { char *jid;
--- a/libpurple/protocols/jabber/chat.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/jabber/chat.c Thu Aug 13 17:15:06 2009 +0000 @@ -605,7 +605,7 @@ jm->to = g_strdup_printf("%s@%s", chat->room, chat->server); if (topic && *topic) - jm->subject = purple_markup_strip_html(topic); + jm->subject = g_strdup(topic); else jm->subject = g_strdup(""); @@ -750,6 +750,7 @@ if(!server || !*server) { purple_notify_error(js->gc, _("Invalid Server"), _("Invalid Server"), NULL); + purple_roomlist_set_in_progress(js->roomlist, FALSE); return; }
--- a/libpurple/protocols/jabber/disco.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/jabber/disco.c Thu Aug 13 17:15:06 2009 +0000 @@ -85,11 +85,11 @@ /* TODO: When we support zeroconf proxies, fix this to handle them */ if (!(sh->jid && sh->host && sh->port > 0)) { + js->bs_proxies = g_list_remove(js->bs_proxies, sh); g_free(sh->jid); g_free(sh->host); g_free(sh->zeroconf); g_free(sh); - js->bs_proxies = g_list_remove(js->bs_proxies, sh); } } @@ -98,10 +98,6 @@ JabberIqType type, const char *id, xmlnode *in_query) { - - if(!from) - return; - if(type == JABBER_IQ_GET) { xmlnode *query, *identity, *feature; JabberIq *iq; @@ -116,7 +112,8 @@ jabber_iq_set_id(iq, id); - xmlnode_set_attrib(iq->node, "to", from); + if (from) + xmlnode_set_attrib(iq->node, "to", from); query = xmlnode_get_child(iq->node, "query"); if(node) @@ -166,6 +163,18 @@ */ xmlnode *feature = xmlnode_new_child(query, "feature"); xmlnode_set_attrib(feature, "var", "http://www.google.com/xmpp/protocol/video/v1"); + } else if (g_str_equal(node, CAPS0115_NODE "#" "camera-v1")) { + /* + * HUGE HACK! We advertise this ext (see jabber_presence_create_js + * where we add <c/> to the <presence/>) for the Google Talk + * clients that don't actually check disco#info features. + * + * This specific feature is redundant but is what + * node='http://mail.google.com/xmpp/client/caps', ver='1.1' + * advertises as 'camera-v1'. + */ + xmlnode *feature = xmlnode_new_child(query, "feature"); + xmlnode_set_attrib(feature, "var", "http://www.google.com/xmpp/protocol/camera/v1"); #endif } else { xmlnode *error, *inf; @@ -196,7 +205,8 @@ xmlnode_set_namespace(bad_request, "urn:ietf:params:xml:ns:xmpp-stanzas"); jabber_iq_set_id(iq, id); - xmlnode_set_attrib(iq->node, "to", from); + if (from) + xmlnode_set_attrib(iq->node, "to", from); jabber_iq_send(iq); }
--- a/libpurple/protocols/jabber/google.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/jabber/google.c Thu Aug 13 17:15:06 2009 +0000 @@ -430,7 +430,7 @@ return (session->media != NULL) ? TRUE : FALSE; } -static void +static gboolean google_session_handle_initiate(JabberStream *js, GoogleSession *session, xmlnode *sess, const char *iq_id) { JabberIq *result; @@ -443,7 +443,7 @@ if (session->state != UNINIT) { purple_debug_error("jabber", "Received initiate for active session.\n"); - return; + return FALSE; } desc_element = xmlnode_get_child(sess, "description"); @@ -456,7 +456,7 @@ else { purple_debug_error("jabber", "Received initiate with " "invalid namespace %s.\n", xmlns); - return; + return FALSE; } session->media = purple_media_manager_create_media( @@ -480,7 +480,7 @@ PURPLE_MEDIA_INFO_HANGUP, NULL, NULL, TRUE); google_session_send_terminate(session); g_free(params); - return; + return FALSE; } g_free(params); @@ -551,6 +551,8 @@ jabber_iq_set_id(result, iq_id); xmlnode_set_attrib(result->node, "to", session->remote_jid); jabber_iq_send(result); + + return TRUE; } static void @@ -776,7 +778,8 @@ session->js = js; session->remote_jid = g_strdup(session->id.initiator); - google_session_parse_iq(js, session, session_node, iq_id); + if (!google_session_handle_initiate(js, session, session_node, iq_id)) + google_session_destroy(session); } #endif /* USE_VV */
--- a/libpurple/protocols/jabber/iq.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/jabber/iq.c Thu Aug 13 17:15:06 2009 +0000 @@ -282,11 +282,6 @@ id = xmlnode_get_attrib(packet, "id"); iq_type = xmlnode_get_attrib(packet, "type"); - signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(jabber_plugin, - "jabber-receiving-iq", js->gc, iq_type, id, from, packet)); - if (signal_return) - return; - /* * child will be either the first tag child or NULL if there is no child. * Historically, we used just the 'query' subchild, but newer XEPs use @@ -345,6 +340,11 @@ return; } + signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(jabber_plugin, + "jabber-receiving-iq", js->gc, iq_type, id, from, packet)); + if (signal_return) + return; + /* First, lets see if a special callback got registered */ if(type == JABBER_IQ_RESULT || type == JABBER_IQ_ERROR) { if((jcd = g_hash_table_lookup(js->iq_callbacks, id))) {
--- a/libpurple/protocols/jabber/jabber.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/jabber/jabber.c Thu Aug 13 17:15:06 2009 +0000 @@ -132,15 +132,19 @@ xmlnode *jid; char *full_jid; if((jid = xmlnode_get_child(bind, "jid")) && (full_jid = xmlnode_get_data(jid))) { - JabberBuddy *my_jb = NULL; jabber_id_free(js->user); - if(!(js->user = jabber_id_new(full_jid))) { + + js->user = jabber_id_new(full_jid); + if (js->user == NULL) { purple_connection_error_reason(js->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Invalid response from server")); + g_free(full_jid); + return; } - if((my_jb = jabber_buddy_find(js, full_jid, TRUE))) - my_jb->subscription |= JABBER_SUB_BOTH; + + js->user_jb = jabber_buddy_find(js, full_jid, TRUE); + js->user_jb->subscription |= JABBER_SUB_BOTH; purple_connection_set_display_name(js->gc, full_jid); @@ -441,7 +445,7 @@ if (len == -1) len = strlen(data); - if (js->use_bosh) + if (js->bosh) jabber_bosh_connection_send_raw(js->bosh, data); else do_jabber_send_raw(js, data, len); @@ -465,7 +469,7 @@ return; js = purple_connection_get_protocol_data(pc); - if (js->use_bosh) + if (js->bosh) if (g_str_equal((*packet)->name, "message") || g_str_equal((*packet)->name, "iq") || g_str_equal((*packet)->name, "presence")) @@ -632,7 +636,6 @@ if (!strcmp(token[0], "_xmpp-client-xbosh")) { purple_debug_info("jabber","Found alternative connection method using %s at %s.\n", token[0], token[1]); js->bosh = jabber_bosh_connection_init(js, token[1]); - js->use_bosh = TRUE; g_strfreev(token); break; } @@ -776,7 +779,6 @@ { PurpleConnection *gc = purple_account_get_connection(account); JabberStream *js; - JabberBuddy *my_jb; PurplePresence *presence; gchar *user; gchar *slash; @@ -811,9 +813,9 @@ js->buddies = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)jabber_buddy_free); - my_jb = jabber_buddy_find(js, user, TRUE); + js->user_jb = jabber_buddy_find(js, user, TRUE); g_free(user); - if (!my_jb) { + if (!js->user_jb) { /* This basically *can't* fail, but for good measure... */ purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_INVALID_SETTINGS, @@ -822,7 +824,7 @@ g_return_val_if_reached(NULL); } - my_jb->subscription |= JABBER_SUB_BOTH; + js->user_jb->subscription |= JABBER_SUB_BOTH; js->iq_callbacks = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); @@ -865,7 +867,6 @@ * attached to that choice, though. */ if (*bosh_url) { - js->use_bosh = TRUE; js->bosh = jabber_bosh_connection_init(js, bosh_url); if (js->bosh) jabber_bosh_connection_connect(js->bosh); @@ -1445,7 +1446,7 @@ * on some SSL backends. */ if (!gc->disconnect_timeout) { - if (js->use_bosh) + if (js->bosh) jabber_bosh_connection_close(js->bosh); else if ((js->gsc && js->gsc->fd > 0) || js->fd > 0) jabber_send_raw(js, "</stream:stream>", -1); @@ -1542,6 +1543,9 @@ g_free(js->old_track); g_free(js->expected_rspauth); + if (js->vcard_timer != 0) + purple_timeout_remove(js->vcard_timer); + if (js->keepalive_timeout != 0) purple_timeout_remove(js->keepalive_timeout); @@ -3467,6 +3471,8 @@ #ifdef USE_VV jabber_add_feature("http://www.google.com/xmpp/protocol/session", jabber_audio_enabled); jabber_add_feature("http://www.google.com/xmpp/protocol/voice/v1", jabber_audio_enabled); + jabber_add_feature("http://www.google.com/xmpp/protocol/video/v1", jabber_video_enabled); + jabber_add_feature("http://www.google.com/xmpp/protocol/camera/v1", jabber_video_enabled); jabber_add_feature(JINGLE_APP_RTP_SUPPORT_AUDIO, jabber_audio_enabled); jabber_add_feature(JINGLE_APP_RTP_SUPPORT_VIDEO, jabber_video_enabled); jabber_add_feature(JINGLE_TRANSPORT_ICEUDP, 0);
--- a/libpurple/protocols/jabber/jabber.h Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/jabber/jabber.h Thu Aug 13 17:15:06 2009 +0000 @@ -164,6 +164,8 @@ time_t old_idle; JabberID *user; + JabberBuddy *user_jb; + PurpleConnection *gc; PurpleSslConnection *gsc; @@ -214,6 +216,8 @@ void *unregistration_user_data; gboolean vcard_fetched; + /* Timer at login to push updated avatar */ + guint vcard_timer; /* Entity Capabilities hash */ char *caps_hash; @@ -251,7 +255,6 @@ guint max_srv_rec_idx; /* BOSH stuff */ - gboolean use_bosh; PurpleBOSHConnection *bosh; /**
--- a/libpurple/protocols/jabber/jingle/rtp.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/jabber/jingle/rtp.c Thu Aug 13 17:15:06 2009 +0000 @@ -355,14 +355,6 @@ static void jingle_rtp_ready(JingleSession *session); static void -jingle_rtp_accepted_cb(PurpleMedia *media, gchar *sid, gchar *name, - JingleSession *session) -{ - purple_debug_info("jingle-rtp", "jingle_rtp_accepted_cb\n"); - jingle_rtp_ready(session); -} - -static void jingle_rtp_candidates_prepared_cb(PurpleMedia *media, gchar *sid, gchar *name, JingleSession *session) { @@ -480,6 +472,9 @@ jabber_iq_send(jingle_session_terminate_packet( session, "decline")); g_object_unref(session); + } else if (type == PURPLE_MEDIA_INFO_ACCEPT && + jingle_session_is_initiator(session) == FALSE) { + jingle_rtp_ready(session); } } @@ -504,8 +499,6 @@ } g_signal_handlers_disconnect_by_func(G_OBJECT(media), - G_CALLBACK(jingle_rtp_accepted_cb), session); - g_signal_handlers_disconnect_by_func(G_OBJECT(media), G_CALLBACK(jingle_rtp_candidates_prepared_cb), session); g_signal_handlers_disconnect_by_func(G_OBJECT(media), @@ -539,9 +532,6 @@ purple_media_set_prpl_data(media, session); /* connect callbacks */ - if (jingle_session_is_initiator(session) == FALSE) - g_signal_connect(G_OBJECT(media), "accepted", - G_CALLBACK(jingle_rtp_accepted_cb), session); g_signal_connect(G_OBJECT(media), "candidates-prepared", G_CALLBACK(jingle_rtp_candidates_prepared_cb), session); g_signal_connect(G_OBJECT(media), "codecs-changed", @@ -599,8 +589,8 @@ if (!strcmp(senders, "both")) type = is_audio == TRUE ? PURPLE_MEDIA_AUDIO : PURPLE_MEDIA_VIDEO; - else if (!strcmp(senders, "initiator") - && jingle_session_is_initiator(session)) + else if ((strcmp(senders, "initiator") == 0) == + jingle_session_is_initiator(session)) type = is_audio == TRUE ? PURPLE_MEDIA_SEND_AUDIO : PURPLE_MEDIA_SEND_VIDEO; else
--- a/libpurple/protocols/jabber/libxmpp.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/jabber/libxmpp.c Thu Aug 13 17:15:06 2009 +0000 @@ -28,6 +28,7 @@ #include "internal.h" #include "accountopt.h" +#include "core.h" #include "debug.h" #include "version.h" @@ -47,6 +48,8 @@ #include "data.h" #include "ibb.h" +static PurplePlugin *my_protocol = NULL; + static PurplePluginProtocolInfo prpl_info = { OPT_PROTO_CHAT_TOPIC | OPT_PROTO_UNIQUE_CHATNAME | OPT_PROTO_MAIL_CHECK | @@ -266,6 +269,70 @@ NULL }; +static PurpleAccount *find_acct(const char *prpl, const char *acct_id) +{ + PurpleAccount *acct = NULL; + + /* If we have a specific acct, use it */ + if (acct_id) { + acct = purple_accounts_find(acct_id, prpl); + if (acct && !purple_account_is_connected(acct)) + acct = NULL; + } else { /* Otherwise find an active account for the protocol */ + GList *l = purple_accounts_get_all(); + while (l) { + if (!strcmp(prpl, purple_account_get_protocol_id(l->data)) + && purple_account_is_connected(l->data)) { + acct = l->data; + break; + } + l = l->next; + } + } + + return acct; +} + +static gboolean xmpp_uri_handler(const char *proto, const char *user, GHashTable *params) +{ + char *acct_id = g_hash_table_lookup(params, "account"); + PurpleAccount *acct; + + if (g_ascii_strcasecmp(proto, "xmpp")) + return FALSE; + + acct = find_acct(purple_plugin_get_id(my_protocol), acct_id); + + if (!acct) + return FALSE; + + /* xmpp:romeo@montague.net?message;subject=Test%20Message;body=Here%27s%20a%20test%20message */ + if (g_hash_table_lookup_extended(params, "message", NULL, NULL)) { + char *body = g_hash_table_lookup(params, "body"); + if (user && *user) { + PurpleConversation *conv = + purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, user); + purple_conversation_present(conv); + if (body && *body) + purple_conv_send_confirm(conv, body); + } + } else if (g_hash_table_lookup_extended(params, "roster", NULL, NULL)) { + char *name = g_hash_table_lookup(params, "name"); + if (user && *user) + purple_blist_request_add_buddy(acct, user, NULL, name); + } else if (g_hash_table_lookup_extended(params, "join", NULL, NULL)) { + PurpleConnection *gc = purple_account_get_connection(acct); + if (user && *user) { + GHashTable *params = jabber_chat_info_defaults(gc, user); + jabber_chat_join(gc, params); + } + return TRUE; + } + + return FALSE; +} + + static void init_plugin(PurplePlugin *plugin) { @@ -331,6 +398,7 @@ prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); + my_protocol = plugin; jabber_init_plugin(plugin); purple_prefs_remove("/plugins/prpl/jabber"); @@ -366,6 +434,9 @@ jabber_ibb_init(); jabber_si_init(); + + purple_signal_connect(purple_get_core(), "uri-handler", plugin, + PURPLE_CALLBACK(xmpp_uri_handler), NULL); }
--- a/libpurple/protocols/jabber/presence.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/jabber/presence.c Thu Aug 13 17:15:06 2009 +0000 @@ -324,11 +324,11 @@ video_enabled = jabber_video_enabled(js, NULL /* unused */); if (audio_enabled && video_enabled) - xmlnode_set_attrib(c, "ext", "voice-v1 video-v1"); + xmlnode_set_attrib(c, "ext", "voice-v1 camera-v1 video-v1"); else if (audio_enabled) xmlnode_set_attrib(c, "ext", "voice-v1"); else if (video_enabled) - xmlnode_set_attrib(c, "ext", "video-v1"); + xmlnode_set_attrib(c, "ext", "camera-v1 video-v1"); #endif return presence; @@ -698,6 +698,7 @@ const char *real_jid = NULL; const char *affiliation = NULL; const char *role = NULL; + gboolean is_our_resource = FALSE; /* Is the presence about us? */ /* * XEP-0045 mandates the presence to include a resource (which is @@ -718,10 +719,15 @@ xmlnode *status_node; xmlnode *item_node; - status_node = xmlnode_get_child(x, "status"); - if (status_node) { + for (status_node = xmlnode_get_child(x, "status"); status_node; + status_node = xmlnode_get_next_twin(status_node)) { const char *code = xmlnode_get_attrib(status_node, "code"); - if (purple_strequal(code, "201")) { + if (!code) + continue; + + if (g_str_equal(code, "110")) { + is_our_resource = TRUE; + } else if (g_str_equal(code, "201")) { if ((chat = jabber_chat_find(js, jid->node, jid->domain))) { chat->config_dialog_type = PURPLE_REQUEST_ACTION; chat->config_dialog_handle = @@ -737,7 +743,7 @@ _("_Configure Room"), G_CALLBACK(jabber_chat_request_room_configure), _("_Accept Defaults"), G_CALLBACK(jabber_chat_create_instant_room)); } - } else if (purple_strequal(code, "210")) { + } else if (g_str_equal(code, "210")) { /* server rewrote room-nick */ if((chat = jabber_chat_find(js, jid->node, jid->domain))) { g_free(chat->handle); @@ -814,7 +820,6 @@ "http://jabber.org/protocol/muc#user"); if (chat->muc && x) { const char *nick; - const char *code = NULL; const char *item_jid = NULL; const char *to; xmlnode *stat; @@ -824,13 +829,16 @@ if (item) item_jid = xmlnode_get_attrib(item, "jid"); - stat = xmlnode_get_child(x, "status"); + for (stat = xmlnode_get_child(x, "status"); stat; + stat = xmlnode_get_next_twin(stat)) { + const char *code = xmlnode_get_attrib(stat, "code"); - if (stat) - code = xmlnode_get_attrib(stat, "code"); + if (!code) + continue; - if (code) { - if(!strcmp(code, "301")) { + if (g_str_equal(code, "110")) { + is_our_resource = TRUE; + } else if(!strcmp(code, "301")) { /* XXX: we got banned */ } else if(!strcmp(code, "303") && item && (nick = xmlnode_get_attrib(item, "nick"))) { @@ -839,10 +847,11 @@ g_free(chat->handle); chat->handle = g_strdup(nick); } + + /* TODO: This should probably be moved out of the loop */ purple_conv_chat_rename_user(PURPLE_CONV_CHAT(chat->conv), jid->resource, nick); jabber_chat_remove_handle(chat, jid->resource); - /* TODO: Enable this when this is in a for-loop... - break; */ + continue; } else if(!strcmp(code, "307")) { /* Someone was kicked from the room */ xmlnode *reason = NULL, *actor = NULL;
--- a/libpurple/protocols/jabber/roster.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/jabber/roster.c Thu Aug 13 17:15:06 2009 +0000 @@ -66,7 +66,7 @@ } static void add_purple_buddy_to_groups(JabberStream *js, const char *jid, - const char *alias, GSList *groups, const char *own_jid) + const char *alias, GSList *groups) { GSList *buddies, *l; GSList *pool = NULL; @@ -162,12 +162,6 @@ purple_blist_add_buddy(b, NULL, g, NULL); purple_blist_alias_buddy(b, alias); - /* If we just learned about ourself, then fake our status, - * because we won't be receiving a normal presence message - * about ourself. */ - if(!strcmp(purple_buddy_get_name(b), own_jid)) - jabber_presence_fake_to_self(js, NULL); - g_free(groups->data); groups = g_slist_delete_link(groups, groups); } @@ -187,7 +181,6 @@ JabberIqType type, const char *id, xmlnode *query) { xmlnode *item, *group; - gchar *own_jid; if (!jabber_is_own_account(js, from)) { purple_debug_warning("jabber", "Received bogon roster push from %s\n", @@ -197,8 +190,6 @@ js->currently_parsing_roster_push = TRUE; - own_jid = g_strdup_printf("%s@%s", js->user->node, js->user->domain); - for(item = xmlnode_get_child(query, "item"); item; item = xmlnode_get_next_twin(item)) { const char *jid, *name, *subscription, *ask; @@ -216,11 +207,7 @@ continue; if(subscription) { - gboolean me = FALSE; - - me = g_str_equal(own_jid, jabber_normalize(js->gc->account, jid)); - - if(me) + if (jb == js->user_jb) jb->subscription = JABBER_SUB_BOTH; else if(!strcmp(subscription, "none")) jb->subscription = JABBER_SUB_NONE; @@ -260,11 +247,12 @@ groups = g_slist_prepend(groups, group_name); } - add_purple_buddy_to_groups(js, jid, name, groups, own_jid); + add_purple_buddy_to_groups(js, jid, name, groups); + if (jb == js->user_jb) + jabber_presence_fake_to_self(js, NULL); } } - g_free(own_jid); js->currently_parsing_roster_push = FALSE; /* if we're just now parsing the roster for the first time, @@ -348,7 +336,6 @@ char *who; JabberBuddy *jb; JabberBuddyResource *jbr; - char *own_jid; const char *name; /* If we haven't received the roster yet, ignore any adds */ @@ -366,8 +353,7 @@ jabber_roster_update(js, who, NULL); - own_jid = g_strdup_printf("%s@%s", js->user->node, js->user->domain); - if (g_str_equal(who, own_jid)) { + if (jb == js->user_jb) { jabber_presence_fake_to_self(js, NULL); } else if(!jb || !(jb->subscription & JABBER_SUB_TO)) { jabber_presence_subscription_set(js, who, "subscribe"); @@ -377,7 +363,6 @@ "priority", jbr->priority, jbr->status ? "message" : NULL, jbr->status, NULL); } - g_free(own_jid); g_free(who); }
--- a/libpurple/protocols/jabber/si.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/jabber/si.c Thu Aug 13 17:15:06 2009 +0000 @@ -68,7 +68,7 @@ JabberIBBSession *ibb_session; guint ibb_timeout_handle; - FILE *fp; + PurpleCircBuffer *ibb_buffer; } JabberSIXfer; /* some forward declarations */ @@ -1013,18 +1013,8 @@ if (size <= purple_xfer_get_bytes_remaining(xfer)) { purple_debug_info("jabber", "about to write %" G_GSIZE_FORMAT " bytes from IBB stream\n", size); - if(!fwrite(data, size, 1, jsx->fp)) { - purple_debug_error("jabber", "error writing to file\n"); - purple_xfer_cancel_remote(xfer); - return; - } - purple_xfer_set_bytes_sent(xfer, purple_xfer_get_bytes_sent(xfer) + size); - purple_xfer_update_progress(xfer); - - if (purple_xfer_get_bytes_remaining(xfer) == 0) { - purple_xfer_set_completed(xfer, TRUE); - purple_xfer_end(xfer); - } + purple_circ_buffer_append(jsx->ibb_buffer, data, size); + purple_xfer_prpl_ready(xfer); } else { /* trying to write past size of file transfers negotiated size, reject transfer to protect against malicious behaviour */ @@ -1035,6 +1025,25 @@ } +static gssize +jabber_si_xfer_ibb_read(guchar **out_buffer, PurpleXfer *xfer) +{ + JabberSIXfer *jsx = xfer->data; + guchar *buffer; + gsize size; + gsize tmp; + + size = jsx->ibb_buffer->bufused; + *out_buffer = buffer = g_malloc(size); + while ((tmp = purple_circ_buffer_get_max_read(jsx->ibb_buffer))) { + memcpy(buffer, jsx->ibb_buffer->outptr, tmp); + buffer += tmp; + purple_circ_buffer_mark_read(jsx->ibb_buffer, tmp); + } + + return size; +} + static gboolean jabber_si_xfer_ibb_open_cb(JabberStream *js, const char *who, const char *id, xmlnode *open) @@ -1045,21 +1054,10 @@ JabberSIXfer *jsx = (JabberSIXfer *) xfer->data; JabberIBBSession *sess = jabber_ibb_session_create_from_xmlnode(js, who, id, open, xfer); - const char *filename; jabber_si_bytestreams_ibb_timeout_remove(jsx); if (sess) { - /* open the file to write to */ - filename = purple_xfer_get_local_filename(xfer); - jsx->fp = g_fopen(filename, "wb"); - if (jsx->fp == NULL) { - purple_debug_error("jabber", "failed to open file %s for writing: %s\n", - filename, g_strerror(errno)); - purple_xfer_cancel_remote(xfer); - return FALSE; - } - /* setup callbacks here...*/ jabber_ibb_session_set_data_received_callback(sess, jabber_si_xfer_ibb_recv_data_cb); @@ -1069,9 +1067,14 @@ jabber_si_xfer_ibb_error_cb); jsx->ibb_session = sess; + jsx->ibb_buffer = + purple_circ_buffer_new(jabber_ibb_session_get_block_size(sess)); + + /* set up read function */ + purple_xfer_set_read_fnc(xfer, jabber_si_xfer_ibb_read); /* start the transfer */ - purple_xfer_start(xfer, 0, NULL, 0); + purple_xfer_start(xfer, -1, NULL, 0); return TRUE; } else { /* failed to create IBB session */ @@ -1087,32 +1090,17 @@ } } -static void -jabber_si_xfer_ibb_send_data(JabberIBBSession *sess) +static gssize +jabber_si_xfer_ibb_write(const guchar *buffer, size_t len, PurpleXfer *xfer) { - PurpleXfer *xfer = (PurpleXfer *) jabber_ibb_session_get_user_data(sess); JabberSIXfer *jsx = (JabberSIXfer *) xfer->data; - gsize remaining = purple_xfer_get_bytes_remaining(xfer); - gsize packet_size = remaining < jabber_ibb_session_get_block_size(sess) ? - remaining : jabber_ibb_session_get_block_size(sess); - gpointer data = g_malloc(packet_size); - int res; + JabberIBBSession *sess = jsx->ibb_session; + gsize packet_size = len < jabber_ibb_session_get_block_size(sess) ? + len : jabber_ibb_session_get_block_size(sess); - purple_debug_info("jabber", "IBB: about to read %" G_GSIZE_FORMAT " bytes from file %p\n", - packet_size, jsx->fp); - res = fread(data, packet_size, 1, jsx->fp); + jabber_ibb_session_send_data(sess, buffer, packet_size); - if (res == 1) { - jabber_ibb_session_send_data(sess, data, packet_size); - purple_xfer_set_bytes_sent(xfer, - purple_xfer_get_bytes_sent(xfer) + packet_size); - purple_xfer_update_progress(xfer); - } else { - purple_debug_error("jabber", - "jabber_si_xfer_ibb_send_data: error reading from file\n"); - purple_xfer_cancel_local(xfer); - } - g_free(data); + return packet_size; } static void @@ -1128,7 +1116,7 @@ purple_xfer_end(xfer); } else { /* send more... */ - jabber_si_xfer_ibb_send_data(sess); + purple_xfer_prpl_ready(xfer); } } @@ -1136,28 +1124,13 @@ jabber_si_xfer_ibb_opened_cb(JabberIBBSession *sess) { PurpleXfer *xfer = (PurpleXfer *) jabber_ibb_session_get_user_data(sess); - JabberSIXfer *jsx = (JabberSIXfer *) xfer->data; JabberStream *js = jabber_ibb_session_get_js(sess); PurpleConnection *gc = js->gc; PurpleAccount *account = purple_connection_get_account(gc); if (jabber_ibb_session_get_state(sess) == JABBER_IBB_SESSION_OPENED) { - const char *filename = purple_xfer_get_local_filename(xfer); - jsx->fp = g_fopen(filename, "rb"); - if (jsx->fp == NULL) { - purple_debug_error("jabber", "Failed to open file %s for reading: %s\n", - filename, g_strerror(errno)); - purple_xfer_error(purple_xfer_get_type(xfer), account, - jabber_ibb_session_get_who(sess), - _("Failed to open the file")); - purple_xfer_cancel_local(xfer); - return; - } - - purple_xfer_start(xfer, 0, NULL, 0); - purple_xfer_set_bytes_sent(xfer, 0); - purple_xfer_update_progress(xfer); - jabber_si_xfer_ibb_send_data(sess); + purple_xfer_start(xfer, -1, NULL, 0); + purple_xfer_prpl_ready(xfer); } else { /* error */ purple_xfer_error(purple_xfer_get_type(xfer), account, @@ -1172,8 +1145,6 @@ { JabberSIXfer *jsx = (JabberSIXfer *) xfer->data; - purple_xfer_ref(xfer); - jsx->ibb_session = jabber_ibb_session_create(js, jsx->stream_id, purple_xfer_get_remote_user(xfer), xfer); @@ -1188,6 +1159,11 @@ jabber_ibb_session_set_error_callback(jsx->ibb_session, jabber_si_xfer_ibb_error_cb); + purple_xfer_set_write_fnc(xfer, jabber_si_xfer_ibb_write); + + jsx->ibb_buffer = + purple_circ_buffer_new(jabber_ibb_session_get_block_size(jsx->ibb_session)); + /* open the IBB session */ jabber_ibb_session_open(jsx->ibb_session); @@ -1359,20 +1335,18 @@ jabber_ibb_session_destroy(jsx->ibb_session); } - if (jsx->fp) { - purple_debug_info("jabber", - "jabber_si_xfer_free: closing file for IBB transfer\n"); - fclose(jsx->fp); + if (jsx->ibb_buffer) { + purple_circ_buffer_destroy(jsx->ibb_buffer); } + purple_debug_info("jabber", "jabber_si_xfer_free(): freeing jsx %p\n", jsx); + g_free(jsx->stream_id); g_free(jsx->iq_id); /* XXX: free other stuff */ g_free(jsx->rxqueue); g_free(jsx); xfer->data = NULL; - - purple_debug_info("jabber", "jabber_si_xfer_free(): freeing jsx %p\n", jsx); } } @@ -1648,7 +1622,6 @@ jsx->local_streamhost_fd = -1; jsx->ibb_session = NULL; - jsx->fp = NULL; purple_xfer_set_init_fnc(xfer, jabber_si_xfer_init); purple_xfer_set_cancel_send_fnc(xfer, jabber_si_xfer_cancel_send);
--- a/libpurple/protocols/jabber/useravatar.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/jabber/useravatar.c Thu Aug 13 17:15:06 2009 +0000 @@ -219,16 +219,18 @@ const char *server_hash = NULL; if (items && (item = xmlnode_get_child(items, "item")) && - (metadata = xmlnode_get_child(item, "metadata")) && - (info = xmlnode_get_child(metadata, "info"))) { + (metadata = xmlnode_get_child(item, "metadata")) && + (info = xmlnode_get_child(metadata, "info"))) { server_hash = xmlnode_get_attrib(info, "id"); } - if (items && !metadata) - return; - - /* Publish ours if it's different than the server's */ - if (!purple_strequal(server_hash, js->initial_avatar_hash)) { + /* + * If we have an avatar and the server returned an error/malformed data, + * push our avatar. If the server avatar doesn't match the local one, push + * our avatar. + */ + if (((!items || !metadata) && js->initial_avatar_hash) || + !purple_strequal(server_hash, js->initial_avatar_hash)) { PurpleStoredImage *img = purple_buddy_icons_find_account_icon(account); jabber_avatar_set(js, img); purple_imgstore_unref(img);
--- a/libpurple/protocols/msn/cmdproc.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/msn/cmdproc.c Thu Aug 13 17:15:06 2009 +0000 @@ -333,60 +333,38 @@ trans->timer = 0; } - if (g_ascii_isdigit(cmd->command[0])) + if (g_ascii_isdigit(cmd->command[0]) && trans != NULL) { - if (trans != NULL) - { - MsnErrorCb error_cb = NULL; - int error; + MsnErrorCb error_cb; + int error; - error = atoi(cmd->command); - - if (trans->error_cb != NULL) - error_cb = trans->error_cb; - - if (error_cb == NULL && cmdproc->cbs_table->errors != NULL) - error_cb = g_hash_table_lookup(cmdproc->cbs_table->errors, trans->command); + error = atoi(cmd->command); - if (error_cb != NULL) - { - error_cb(cmdproc, trans, error); - } - else - { -#if 1 - msn_error_handle(cmdproc->session, error); -#else - purple_debug_warning("msn", "Unhandled error '%s'\n", - cmd->command); -#endif - } + error_cb = trans->error_cb; + if (error_cb == NULL) + error_cb = g_hash_table_lookup(cmdproc->cbs_table->errors, trans->command); - return; - } + if (error_cb != NULL) + error_cb(cmdproc, trans, error); + else + msn_error_handle(cmdproc->session, error); + + return; } - if (cmdproc->cbs_table->async != NULL) - cb = g_hash_table_lookup(cmdproc->cbs_table->async, cmd->command); + cb = g_hash_table_lookup(cmdproc->cbs_table->async, cmd->command); - if (cb == NULL && trans != NULL) - { - if (trans->callbacks != NULL) - cb = g_hash_table_lookup(trans->callbacks, cmd->command); - } + if (cb == NULL && trans != NULL && trans->callbacks != NULL) + cb = g_hash_table_lookup(trans->callbacks, cmd->command); - if (cb == NULL && cmdproc->cbs_table->fallback != NULL) + if (cb == NULL) cb = g_hash_table_lookup(cmdproc->cbs_table->fallback, cmd->command); if (cb != NULL) - { cb(cmdproc, cmd); - } else - { purple_debug_warning("msn", "Unhandled command '%s'\n", cmd->command); - } if (trans != NULL && trans->pendent_cmd != NULL) msn_transaction_unqueue_cmd(trans, cmdproc);
--- a/libpurple/protocols/msn/contact.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/msn/contact.c Thu Aug 13 17:15:06 2009 +0000 @@ -351,14 +351,24 @@ msn_parse_each_member(MsnSession *session, xmlnode *member, const char *node, MsnListId list) { - char *passport = xmlnode_get_data(xmlnode_get_child(member, node)); - char *type = xmlnode_get_data(xmlnode_get_child(member, "Type")); - char *member_id = xmlnode_get_data(xmlnode_get_child(member, "MembershipId")); - MsnUser *user = msn_userlist_find_add_user(session->userlist, passport, NULL); + char *passport; + char *type; + char *member_id; + MsnUser *user; xmlnode *annotation; guint nid = MSN_NETWORK_UNKNOWN; char *invite = NULL; + passport = xmlnode_get_data(xmlnode_get_child(member, node)); + if (!purple_email_is_valid(passport)) { + g_free(passport); + return; + } + + type = xmlnode_get_data(xmlnode_get_child(member, "Type")); + member_id = xmlnode_get_data(xmlnode_get_child(member, "MembershipId")); + user = msn_userlist_find_add_user(session->userlist, passport, NULL); + for (annotation = xmlnode_get_child(member, "Annotations/Annotation"); annotation; annotation = xmlnode_get_next_twin(annotation)) { @@ -746,6 +756,9 @@ if (passport == NULL) continue; + if (!purple_email_is_valid(passport)) + continue; + if ((displayName = xmlnode_get_child(contactInfo, "displayName"))) Name = xmlnode_get_data(displayName); else
--- a/libpurple/protocols/msn/directconn.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/msn/directconn.c Thu Aug 13 17:15:06 2009 +0000 @@ -247,14 +247,6 @@ } static void -msn_directconn_process_msg(MsnDirectConn *directconn, MsnMessage *msg) -{ - purple_debug_info("msn", "directconn: process_msg\n"); - - msn_slplink_process_msg(directconn->slplink, msg); -} - -static void read_cb(gpointer data, gint source, PurpleInputCondition cond) { MsnDirectConn* directconn; @@ -267,6 +259,19 @@ directconn = data; /* Let's read the length of the data. */ +#error This code is broken. See the note below. + /* + * TODO: This has problems! First of all, sizeof(body_len) will be + * different on 32bit systems and on 64bit systems (4 bytes + * vs. 8 bytes). + * Secondly, we're reading from a TCP stream. There is no + * guarantee that we have received the number of bytes we're + * trying to read. We need to read into a buffer. If read + * returns <0 then we need to check errno. If errno is EAGAIN + * then don't destroy anything, just exit and wait for more + * data. See every other function in libpurple that does this + * correctly for an example. + */ len = read(directconn->fd, &body_len, sizeof(body_len)); if (len <= 0) @@ -337,7 +342,8 @@ msg = msn_message_new_msnslp(); msn_message_parse_slp_body(msg, body, body_len); - msn_directconn_process_msg(directconn, msg); + purple_debug_info("msn", "directconn: process_msg\n"); + msn_slplink_process_msg(directconn->slplink, msg); } else { @@ -351,6 +357,8 @@ msn_directconn_destroy(directconn); } + + g_free(body); } static void
--- a/libpurple/protocols/msn/msg.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/msn/msg.c Thu Aug 13 17:15:06 2009 +0000 @@ -1102,6 +1102,7 @@ { GHashTable *body; const gchar *guid; + gboolean accepted = FALSE; g_return_if_fail(cmdproc != NULL); g_return_if_fail(msg != NULL); @@ -1129,6 +1130,9 @@ } else purple_debug_warning("msn", "Invite msg missing " "Application-GUID.\n"); + + accepted = TRUE; + } else if (!strcmp(guid, "{02D3C01F-BF30-4825-A83A-DE7AF41648AA}")) { purple_debug_info("msn", "Computer call\n"); @@ -1154,9 +1158,35 @@ g_free(buf); } } - } else - purple_debug_warning("msn", - "Unhandled invite msg with GUID %s.\n", guid); + } else { + const gchar *application = g_hash_table_lookup(body, "Application-Name"); + purple_debug_warning("msn", "Unhandled invite msg with GUID %s: %s.\n", + guid, application ? application : "(null)"); + } + + if (!accepted) { + const gchar *cookie = g_hash_table_lookup(body, "Invitation-Cookie"); + if (cookie) { + MsnSwitchBoard *swboard = cmdproc->data; + char *text; + MsnMessage *cancel; + + cancel = msn_message_new(MSN_MSG_TEXT); + msn_message_set_content_type(cancel, "text/x-msmsgsinvite"); + msn_message_set_charset(cancel, "UTF-8"); + msn_message_set_flag(cancel, 'U'); + + text = g_strdup_printf("Invitation-Command: CANCEL\r\n" + "Invitation-Cookie: %s\r\n" + "Cancel-Code: REJECT_NOT_INSTALLED\r\n", + cookie); + msn_message_set_bin_data(cancel, text, strlen(text)); + g_free(text); + + msn_switchboard_send_msg(swboard, cancel, TRUE); + msn_message_destroy(cancel); + } + } g_hash_table_destroy(body); }
--- a/libpurple/protocols/msn/session.h Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/msn/session.h Thu Aug 13 17:15:06 2009 +0000 @@ -35,7 +35,6 @@ #include "switchboard.h" #include "group.h" -#include "cmdproc.h" #include "nexus.h" #include "httpconn.h" #include "oim.h"
--- a/libpurple/protocols/msn/slp.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/msn/slp.c Thu Aug 13 17:15:06 2009 +0000 @@ -244,6 +244,8 @@ got_sessionreq(MsnSlpCall *slpcall, const char *branch, const char *euf_guid, const char *context) { + gboolean accepted = FALSE; + if (!strcmp(euf_guid, MSN_OBJ_GUID)) { /* Emoticon or UserDisplay */ @@ -314,7 +316,10 @@ msn_slpmsg_set_image(slpmsg, img); msn_slplink_queue_slpmsg(slplink, slpmsg); purple_imgstore_unref(img); + + accepted = TRUE; } + else if (!strcmp(euf_guid, MSN_FT_GUID)) { /* File Transfer */ @@ -324,7 +329,6 @@ gsize bin_len; guint32 file_size; char *file_name; - gunichar2 *uni_name; account = slpcall->slplink->session->account; @@ -342,14 +346,8 @@ bin = (char *)purple_base64_decode(context, &bin_len); file_size = GUINT32_FROM_LE(*(gsize *)(bin + 8)); - uni_name = (gunichar2 *)(bin + 20); - while(*uni_name != 0 && ((char *)uni_name - (bin + 20)) < MAX_FILE_NAME_LEN) { - *uni_name = GUINT16_FROM_LE(*uni_name); - uni_name++; - } - - file_name = g_utf16_to_utf8((const gunichar2 *)(bin + 20), -1, - NULL, NULL, NULL); + file_name = g_convert(bin + 20, MAX_FILE_NAME_LEN, "UTF-8", "UTF-16LE", + NULL, NULL, NULL); g_free(bin); @@ -367,6 +365,9 @@ purple_xfer_request(xfer); } + + accepted = TRUE; + } else if (!strcmp(euf_guid, MSN_CAM_REQUEST_GUID)) { purple_debug_info("msn", "Cam request.\n"); if (slpcall && slpcall->slplink && @@ -389,6 +390,7 @@ g_free(buf); } } + } else if (!strcmp(euf_guid, MSN_CAM_GUID)) { purple_debug_info("msn", "Cam invite.\n"); if (slpcall && slpcall->slplink && @@ -411,8 +413,16 @@ g_free(buf); } } + } else purple_debug_warning("msn", "SLP SessionReq with unknown EUF-GUID: %s\n", euf_guid); + + if (!accepted) { + char *content = g_strdup_printf("SessionID: %lu\r\n\r\n", + slpcall->session_id); + send_decline(slpcall, branch, "application/x-msnmsgr-sessionreqbody", content); + g_free(content); + } } void
--- a/libpurple/protocols/msn/slpcall.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/msn/slpcall.c Thu Aug 13 17:15:06 2009 +0000 @@ -206,20 +206,23 @@ { /* This is for handwritten messages (Ink) */ GError *error; - glong items_read, items_written; + gsize bytes_read, bytes_written; - body_str = g_utf16_to_utf8((gunichar2 *)body, body_len / 2, - &items_read, &items_written, &error); - body_len -= items_read * 2 + 2; - body += items_read * 2 + 2; + body_str = g_convert((const gchar *)body, body_len / 2, + "UTF-8", "UTF-16LE", + &bytes_read, &bytes_written, &error); + body_len -= bytes_read + 2; + body += bytes_read + 2; if (body_str == NULL || body_len <= 0 || strstr(body_str, "image/gif") == NULL) { - if (error != NULL) + if (error != NULL) { purple_debug_error("msn", "Unable to convert Ink header from UTF-16 to UTF-8: %s\n", error->message); + g_error_free(error); + } else purple_debug_error("msn", "Received Ink in unknown format\n"); @@ -228,13 +231,20 @@ } g_free(body_str); - body_str = g_utf16_to_utf8((gunichar2 *)body, body_len / 2, - &items_read, &items_written, &error); + body_str = g_convert((const gchar *)body, body_len / 2, + "UTF-8", "UTF16-LE", + &bytes_read, &bytes_written, &error); if (!body_str) { - purple_debug_error("msn", - "Unable to convert Ink body from UTF-16 to UTF-8: %s\n", - error->message); + if (error != NULL) { + purple_debug_error("msn", + "Unable to convert Ink body from UTF-16 to UTF-8: %s\n", + error->message); + g_error_free(error); + } + else + purple_debug_error("msn", + "Received Ink in unknown format\n"); return NULL; }
--- a/libpurple/protocols/msn/slplink.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/msn/slplink.c Thu Aug 13 17:15:06 2009 +0000 @@ -456,7 +456,7 @@ slpmsg->info = "SLP FILE"; xfer = (PurpleXfer *)slpcall->xfer; - purple_xfer_start(slpcall->xfer, 0, NULL, 0); + purple_xfer_start(slpcall->xfer, -1, NULL, 0); slpmsg->fp = xfer->dest_fp; if (g_stat(purple_xfer_get_local_filename(xfer), &st) == 0) slpmsg->size = st.st_size; @@ -502,7 +502,6 @@ g_return_if_reached(); } - slpmsg = NULL; data = msn_message_get_bin_data(msg, &len); /* @@ -537,11 +536,12 @@ if (xfer != NULL) { purple_xfer_ref(xfer); - purple_xfer_start(xfer, 0, NULL, 0); + purple_xfer_start(xfer, -1, NULL, 0); if (xfer->data == NULL) { purple_xfer_unref(xfer); - return; + msn_slpmsg_destroy(slpmsg); + g_return_if_reached(); } else { purple_xfer_unref(xfer); slpmsg->fp = xfer->dest_fp; @@ -565,13 +565,12 @@ else { slpmsg = msn_slplink_message_find(slplink, msg->msnslp_header.session_id, msg->msnslp_header.id); - } - - if (slpmsg == NULL) - { - /* Probably the transfer was canceled */ - purple_debug_error("msn", "Couldn't find slpmsg\n"); - return; + if (slpmsg == NULL) + { + /* Probably the transfer was canceled */ + purple_debug_error("msn", "Couldn't find slpmsg\n"); + return; + } } if (slpmsg->fp)
--- a/libpurple/protocols/msn/slpmsg.h Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/msn/slpmsg.h Thu Aug 13 17:15:06 2009 +0000 @@ -50,10 +50,8 @@ long ack_id; long ack_sub_id; long long ack_size; - long app_id; gboolean sip; /**< A flag that states if this is a SIP slp message. */ - int ref_count; /**< The reference count. */ long flags; FILE *fp;
--- a/libpurple/protocols/msn/soap.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/msn/soap.c Thu Aug 13 17:15:06 2009 +0000 @@ -385,7 +385,7 @@ msn_soap_connection_handle_next(conn); handled = TRUE; break; - } else if (conn->response_code == 503) { + } else if (conn->response_code == 503 && conn->session->login_step < MSN_LOGIN_STEP_END) { msn_soap_connection_sanitize(conn, TRUE); msn_session_set_error(conn->session, MSN_ERROR_SERV_UNAVAILABLE, NULL); return;
--- a/libpurple/protocols/msn/sync.h Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/msn/sync.h Thu Aug 13 17:15:06 2009 +0000 @@ -34,6 +34,11 @@ { MsnSession *session; MsnTable *cbs_table; + + /* + * TODO: What is the intended purpose of old_cbs_table? Nothing + * sets it and it is only read in two places. + */ MsnTable *old_cbs_table; int num_users;
--- a/libpurple/protocols/msn/userlist.h Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/msn/userlist.h Thu Aug 13 17:15:06 2009 +0000 @@ -26,7 +26,6 @@ typedef struct _MsnUserList MsnUserList; -#include "cmdproc.h" #include "user.h" #include "group.h"
--- a/libpurple/protocols/msnp9/directconn.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/msnp9/directconn.c Thu Aug 13 17:15:06 2009 +0000 @@ -355,6 +355,8 @@ msn_directconn_destroy(directconn); } + + g_free(body); } static void
--- a/libpurple/protocols/msnp9/slplink.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/msnp9/slplink.c Thu Aug 13 17:15:06 2009 +0000 @@ -496,7 +496,7 @@ slpmsg->info = "SLP FILE"; #endif xfer = (PurpleXfer *)slpcall->xfer; - purple_xfer_start(slpcall->xfer, 0, NULL, 0); + purple_xfer_start(slpcall->xfer, -1, NULL, 0); slpmsg->fp = xfer->dest_fp; if (g_stat(purple_xfer_get_local_filename(xfer), &st) == 0) slpmsg->size = st.st_size; @@ -561,7 +561,7 @@ if (xfer != NULL) { purple_xfer_ref(xfer); - purple_xfer_start(xfer, 0, NULL, 0); + purple_xfer_start(xfer, -1, NULL, 0); if (xfer->data == NULL) { purple_xfer_unref(xfer);
--- a/libpurple/protocols/myspace/myspace.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/myspace/myspace.c Thu Aug 13 17:15:06 2009 +0000 @@ -1341,6 +1341,27 @@ return ret; } #endif +/** + * Process incoming status mood messages. + * + * @param session + * @param msg Status mood update message. Caller frees. + * + * @return TRUE if successful. + */ +static gboolean +msim_incoming_status_mood(MsimSession *session, MsimMessage *msg) { + /* TODO: I dont know too much about this yet, + * so until I see how the official client handles + * this and decide if libpurple should as well, + * well just say we used it + */ + gchar *ss; + ss = msim_msg_get_string(msg, "msg"); + purple_debug_info("msim", "Incoming Status Message: %s", ss ? ss : "(NULL)"); + g_free(ss); + return TRUE; +} /** * Process incoming status messages. @@ -1692,11 +1713,19 @@ return msim_incoming_media(session, msg); case MSIM_BM_UNOFFICIAL_CLIENT: return msim_incoming_unofficial_client(session, msg); + case MSIM_BM_STATUS_MOOD: + return msim_incoming_status_mood(session, msg); default: - /* Not really an IM, but show it for informational - * purposes during development. */ - /* TODO: This is probably wrong */ - return msim_incoming_action_or_im(session, msg); + /* + * Unknown message type! We used to call + * msim_incoming_action_or_im(session, msg); + * for these, but that doesn't help anything, and it means + * we'll show broken gibberish if MySpace starts sending us + * other message types. + */ + purple_debug_warning("myspace", "Received unknown imcoming " + "message, bm=%u\n", bm); + return TRUE; } }
--- a/libpurple/protocols/myspace/myspace.h Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/myspace/myspace.h Thu Aug 13 17:15:06 2009 +0000 @@ -132,6 +132,7 @@ #define MSIM_BM_ACTION_OR_IM_INSTANT 121 #define MSIM_BM_MEDIA 122 #define MSIM_BM_PROFILE 124 +#define MSIM_BM_STATUS_MOOD 126 #define MSIM_BM_UNOFFICIAL_CLIENT 200 /* Authentication algorithm for login2 */
--- a/libpurple/protocols/oscar/family_icq.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/oscar/family_icq.c Thu Aug 13 17:15:06 2009 +0000 @@ -246,7 +246,7 @@ if (!od || !(conn = flap_connection_findbygroup(od, SNAC_FAMILY_ICQ))) return -EINVAL; - purple_debug_info("oscar", "Requesting ICQ alias for %s", uin); + purple_debug_info("oscar", "Requesting ICQ alias for %s\n", uin); bslen = 2 + 4 + 2 + 2 + 2 + 4; @@ -735,14 +735,6 @@ info = g_new0(struct aim_icq_info, 1); - if (info == NULL) - { - g_free(uin); - g_free(status_note_title); - - break; - } - bslen = 13 + strlen(uin) + 30 + 6 + 4 + 55 + 85 + 4; byte_stream_new(&bs, 4 + bslen);
--- a/libpurple/protocols/oscar/family_locate.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/oscar/family_locate.c Thu Aug 13 17:15:06 2009 +0000 @@ -615,13 +615,15 @@ * User flags * * Specified as any of the following ORed together: - * 0x0001 Trial (user less than 60days) + * 0x0001 Unconfirmed account * 0x0002 Unknown bit 2 * 0x0004 AOL Main Service user * 0x0008 Unknown bit 4 * 0x0010 Free (AIM) user * 0x0020 Away - * 0x0400 ActiveBuddy + * 0x0040 ICQ user (AIM bit also set) + * 0x0080 Mobile device + * 0x0400 Bot (like ActiveBuddy) */ outinfo->flags = byte_stream_get16(bs); outinfo->present |= AIM_USERINFO_PRESENT_FLAGS;
--- a/libpurple/protocols/yahoo/libymsg.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/yahoo/libymsg.c Thu Aug 13 17:15:06 2009 +0000 @@ -478,7 +478,7 @@ PurpleAccount *account = purple_connection_get_account(gc); YahooData *yd = gc->proto_data; GHashTable *ht; - char *norm_bud = NULL; + char *norm_bud; char *temp = NULL; YahooFriend *f = NULL; /* It's your friends. They're going to want you to share your StarBursts. */ /* But what if you had no friends? */ @@ -487,7 +487,6 @@ int protocol = 0; int stealth = 0; - ht = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_slist_free); while (l) { @@ -546,9 +545,11 @@ purple_privacy_deny_add(account, norm_bud, 1); } + g_free(norm_bud); + protocol = 0; stealth = 0; - norm_bud = NULL; + g_free(temp); temp = NULL; } break; @@ -559,6 +560,7 @@ yd->current_list15_grp = yahoo_string_decode(gc, pair->value, FALSE); break; case 7: /* buddy's s/n */ + g_free(temp); temp = g_strdup(purple_normalize(account, pair->value)); break; case 241: /* another protocol user */ @@ -594,7 +596,6 @@ yahoo_set_status(account, purple_account_get_active_status(account)); g_hash_table_destroy(ht); - g_free(norm_bud); g_free(temp); } @@ -976,7 +977,7 @@ return; } - /** TODO: It seems that this check should be per IM, not global */ + /* TODO: It seems that this check should be per IM, not global */ /* Check for the Doodle IMV */ if (im != NULL && imv!= NULL && im->from != NULL) { @@ -1830,6 +1831,7 @@ g_free(error_reason); g_free(auth_data->seed); g_free(auth_data); + g_free(token); } else { /* OK to login, correct information provided */ @@ -2082,6 +2084,12 @@ msg = g_strdup(_("Your account is locked, please log in to the Yahoo! website.")); reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED; break; + case 52: + /* See #9660. As much as we know, reconnecting shouldn't hurt */ + purple_debug_info("yahoo", "Got error 52, Set to autoreconnect\n"); + msg = g_strdup_printf(_("Unknown error")); + reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; + break; case 1013: msg = g_strdup(_("Invalid username")); reason = PURPLE_CONNECTION_ERROR_INVALID_USERNAME; @@ -2578,8 +2586,7 @@ PurpleAccount *account; YahooData *yd; - if(!(p2p_data = data)) - return ; + p2p_data = data; yd = p2p_data->gc->proto_data; if(error_message != NULL) { @@ -3767,7 +3774,7 @@ if (presence != NULL) purple_notify_user_info_add_pair(user_info, _("Presence"), presence); - if (full) { + if (f && full) { YahooPersonalDetails *ypd = &f->ypd; int i; struct {
--- a/libpurple/protocols/yahoo/util.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/yahoo/util.c Thu Aug 13 17:15:06 2009 +0000 @@ -172,7 +172,6 @@ char *yahoo_convert_to_numeric(const char *str) { GString *gstr = NULL; - char *retstr; const unsigned char *p; gstr = g_string_sized_new(strlen(str) * 6 + 1); @@ -181,121 +180,172 @@ g_string_append_printf(gstr, "&#%u;", *p); } - retstr = gstr->str; - - g_string_free(gstr, FALSE); - - return retstr; + return g_string_free(gstr, FALSE); } /* + * The values in this hash table should probably be lowercase, since that's + * what xhtml expects. Also because yahoo_codes_to_html() does + * case-sensitive comparisons. + * * I found these on some website but i don't know that they actually * work (or are supposed to work). I didn't implement them yet. * - * [0;30m ---black - * [1;37m ---white - * [0;37m ---tan - * [0;38m ---light black - * [1;39m ---dark blue - * [0;32m ---green - * [0;33m ---yellow - * [0;35m ---pink - * [1;35m ---purple - * [1;30m ---light blue - * [0;31m ---red - * [0;34m ---blue - * [0;36m ---aqua - * (shift+comma)lyellow(shift+period) ---light yellow - * (shift+comma)lgreen(shift+period) ---light green -[2;30m <--white out -*/ + * [0;30m ---black + * [1;37m ---white + * [0;37m ---tan + * [0;38m ---light black + * [1;39m ---dark blue + * [0;32m ---green + * [0;33m ---yellow + * [0;35m ---pink + * [1;35m ---purple + * [1;30m ---light blue + * [0;31m ---red + * [0;34m ---blue + * [0;36m ---aqua + * (shift+comma)lyellow(shift+period) ---light yellow + * (shift+comma)lgreen(shift+period) ---light green + * [2;30m <--white out + */ -static GHashTable *ht = NULL; +static GHashTable *esc_codes_ht = NULL; +static GHashTable *tags_ht = NULL; void yahoo_init_colorht() { - if (ht != NULL) + if (esc_codes_ht != NULL) /* Hash table has already been initialized */ return; - ht = g_hash_table_new(g_str_hash, g_str_equal); + /* Key is the escape code string. Value is the HTML that should be + * inserted in place of the escape code. */ + esc_codes_ht = g_hash_table_new(g_str_hash, g_str_equal); + + /* Key is the name of the HTML tag, for example "font" or "/font" + * value is the HTML that should be inserted in place of the old tag */ + tags_ht = g_hash_table_new(g_str_hash, g_str_equal); + /* the numbers in comments are what gyach uses, but i think they're incorrect */ - g_hash_table_insert(ht, "30", "<FONT COLOR=\"#000000\">"); /* black */ - g_hash_table_insert(ht, "31", "<FONT COLOR=\"#0000FF\">"); /* blue */ - g_hash_table_insert(ht, "32", "<FONT COLOR=\"#008080\">"); /* cyan */ /* 00b2b2 */ - g_hash_table_insert(ht, "33", "<FONT COLOR=\"#808080\">"); /* gray */ /* 808080 */ - g_hash_table_insert(ht, "34", "<FONT COLOR=\"#008000\">"); /* green */ /* 00c200 */ - g_hash_table_insert(ht, "35", "<FONT COLOR=\"#FF0080\">"); /* pink */ /* ffafaf */ - g_hash_table_insert(ht, "36", "<FONT COLOR=\"#800080\">"); /* purple */ /* b200b2 */ - g_hash_table_insert(ht, "37", "<FONT COLOR=\"#FF8000\">"); /* orange */ /* ffff00 */ - g_hash_table_insert(ht, "38", "<FONT COLOR=\"#FF0000\">"); /* red */ - g_hash_table_insert(ht, "39", "<FONT COLOR=\"#808000\">"); /* olive */ /* 546b50 */ +#ifdef USE_CSS_FORMATTING + g_hash_table_insert(esc_codes_ht, "30", "<span style=\"color: #000000\">"); /* black */ + g_hash_table_insert(esc_codes_ht, "31", "<span style=\"color: #0000FF\">"); /* blue */ + g_hash_table_insert(esc_codes_ht, "32", "<span style=\"color: #008080\">"); /* cyan */ /* 00b2b2 */ + g_hash_table_insert(esc_codes_ht, "33", "<span style=\"color: #808080\">"); /* gray */ /* 808080 */ + g_hash_table_insert(esc_codes_ht, "34", "<span style=\"color: #008000\">"); /* green */ /* 00c200 */ + g_hash_table_insert(esc_codes_ht, "35", "<span style=\"color: #FF0080\">"); /* pink */ /* ffafaf */ + g_hash_table_insert(esc_codes_ht, "36", "<span style=\"color: #800080\">"); /* purple */ /* b200b2 */ + g_hash_table_insert(esc_codes_ht, "37", "<span style=\"color: #FF8000\">"); /* orange */ /* ffff00 */ + g_hash_table_insert(esc_codes_ht, "38", "<span style=\"color: #FF0000\">"); /* red */ + g_hash_table_insert(esc_codes_ht, "39", "<span style=\"color: #808000\">"); /* olive */ /* 546b50 */ +#else + g_hash_table_insert(esc_codes_ht, "30", "<font color=\"#000000\">"); /* black */ + g_hash_table_insert(esc_codes_ht, "31", "<font color=\"#0000FF\">"); /* blue */ + g_hash_table_insert(esc_codes_ht, "32", "<font color=\"#008080\">"); /* cyan */ /* 00b2b2 */ + g_hash_table_insert(esc_codes_ht, "33", "<font color=\"#808080\">"); /* gray */ /* 808080 */ + g_hash_table_insert(esc_codes_ht, "34", "<font color=\"#008000\">"); /* green */ /* 00c200 */ + g_hash_table_insert(esc_codes_ht, "35", "<font color=\"#FF0080\">"); /* pink */ /* ffafaf */ + g_hash_table_insert(esc_codes_ht, "36", "<font color=\"#800080\">"); /* purple */ /* b200b2 */ + g_hash_table_insert(esc_codes_ht, "37", "<font color=\"#FF8000\">"); /* orange */ /* ffff00 */ + g_hash_table_insert(esc_codes_ht, "38", "<font color=\"#FF0000\">"); /* red */ + g_hash_table_insert(esc_codes_ht, "39", "<font color=\"#808000\">"); /* olive */ /* 546b50 */ +#endif /* !USE_CSS_FORMATTING */ - g_hash_table_insert(ht, "1", "<B>"); - g_hash_table_insert(ht, "x1", "</B>"); - g_hash_table_insert(ht, "2", "<I>"); - g_hash_table_insert(ht, "x2", "</I>"); - g_hash_table_insert(ht, "4", "<U>"); - g_hash_table_insert(ht, "x4", "</U>"); + g_hash_table_insert(esc_codes_ht, "1", "<b>"); + g_hash_table_insert(esc_codes_ht, "x1", "</b>"); + g_hash_table_insert(esc_codes_ht, "2", "<i>"); + g_hash_table_insert(esc_codes_ht, "x2", "</i>"); + g_hash_table_insert(esc_codes_ht, "4", "<u>"); + g_hash_table_insert(esc_codes_ht, "x4", "</u>"); /* these just tell us the text they surround is supposed * to be a link. purple figures that out on its own so we * just ignore it. */ - g_hash_table_insert(ht, "l", ""); /* link start */ - g_hash_table_insert(ht, "xl", ""); /* link end */ + g_hash_table_insert(esc_codes_ht, "l", ""); /* link start */ + g_hash_table_insert(esc_codes_ht, "xl", ""); /* link end */ - g_hash_table_insert(ht, "<black>", "<FONT COLOR=\"#000000\">"); - g_hash_table_insert(ht, "<blue>", "<FONT COLOR=\"#0000FF\">"); - g_hash_table_insert(ht, "<cyan>", "<FONT COLOR=\"#008284\">"); - g_hash_table_insert(ht, "<gray>", "<FONT COLOR=\"#848284\">"); - g_hash_table_insert(ht, "<green>", "<FONT COLOR=\"#008200\">"); - g_hash_table_insert(ht, "<pink>", "<FONT COLOR=\"#FF0084\">"); - g_hash_table_insert(ht, "<purple>", "<FONT COLOR=\"#840084\">"); - g_hash_table_insert(ht, "<orange>", "<FONT COLOR=\"#FF8000\">"); - g_hash_table_insert(ht, "<red>", "<FONT COLOR=\"#FF0000\">"); - g_hash_table_insert(ht, "<yellow>", "<FONT COLOR=\"#848200\">"); +#ifdef USE_CSS_FORMATTING + g_hash_table_insert(tags_ht, "black", "<span style=\"color: #000000\">"); + g_hash_table_insert(tags_ht, "blue", "<span style=\"color: #0000FF\">"); + g_hash_table_insert(tags_ht, "cyan", "<span style=\"color: #008284\">"); + g_hash_table_insert(tags_ht, "gray", "<span style=\"color: #848284\">"); + g_hash_table_insert(tags_ht, "green", "<span style=\"color: #008200\">"); + g_hash_table_insert(tags_ht, "pink", "<span style=\"color: #FF0084\">"); + g_hash_table_insert(tags_ht, "purple", "<span style=\"color: #840084\">"); + g_hash_table_insert(tags_ht, "orange", "<span style=\"color: #FF8000\">"); + g_hash_table_insert(tags_ht, "red", "<span style=\"color: #FF0000\">"); + g_hash_table_insert(tags_ht, "yellow", "<span style=\"color: #848200\">"); - g_hash_table_insert(ht, "</black>", "</FONT>"); - g_hash_table_insert(ht, "</blue>", "</FONT>"); - g_hash_table_insert(ht, "</cyan>", "</FONT>"); - g_hash_table_insert(ht, "</gray>", "</FONT>"); - g_hash_table_insert(ht, "</green>", "</FONT>"); - g_hash_table_insert(ht, "</pink>", "</FONT>"); - g_hash_table_insert(ht, "</purple>", "</FONT>"); - g_hash_table_insert(ht, "</orange>", "</FONT>"); - g_hash_table_insert(ht, "</red>", "</FONT>"); - g_hash_table_insert(ht, "</yellow>", "</FONT>"); + g_hash_table_insert(tags_ht, "/black", "</span>"); + g_hash_table_insert(tags_ht, "/blue", "</span>"); + g_hash_table_insert(tags_ht, "/cyan", "</span>"); + g_hash_table_insert(tags_ht, "/gray", "</span>"); + g_hash_table_insert(tags_ht, "/green", "</span>"); + g_hash_table_insert(tags_ht, "/pink", "</span>"); + g_hash_table_insert(tags_ht, "/purple", "</span>"); + g_hash_table_insert(tags_ht, "/orange", "</span>"); + g_hash_table_insert(tags_ht, "/red", "</span>"); + g_hash_table_insert(tags_ht, "/yellow", "</span>"); +#else + g_hash_table_insert(tags_ht, "black", "<font color=\"#000000\">"); + g_hash_table_insert(tags_ht, "blue", "<font color=\"#0000FF\">"); + g_hash_table_insert(tags_ht, "cyan", "<font color=\"#008284\">"); + g_hash_table_insert(tags_ht, "gray", "<font color=\"#848284\">"); + g_hash_table_insert(tags_ht, "green", "<font color=\"#008200\">"); + g_hash_table_insert(tags_ht, "pink", "<font color=\"#FF0084\">"); + g_hash_table_insert(tags_ht, "purple", "<font color=\"#840084\">"); + g_hash_table_insert(tags_ht, "orange", "<font color=\"#FF8000\">"); + g_hash_table_insert(tags_ht, "red", "<font color=\"#FF0000\">"); + g_hash_table_insert(tags_ht, "yellow", "<font color=\"#848200\">"); - /* remove these once we have proper support for <FADE> and <ALT> */ - g_hash_table_insert(ht, "</fade>", ""); - g_hash_table_insert(ht, "</alt>", ""); + g_hash_table_insert(tags_ht, "/black", "</font>"); + g_hash_table_insert(tags_ht, "/blue", "</font>"); + g_hash_table_insert(tags_ht, "/cyan", "</font>"); + g_hash_table_insert(tags_ht, "/gray", "</font>"); + g_hash_table_insert(tags_ht, "/green", "</font>"); + g_hash_table_insert(tags_ht, "/pink", "</font>"); + g_hash_table_insert(tags_ht, "/purple", "</font>"); + g_hash_table_insert(tags_ht, "/orange", "</font>"); + g_hash_table_insert(tags_ht, "/red", "</font>"); + g_hash_table_insert(tags_ht, "/yellow", "</font>"); +#endif /* !USE_CSS_FORMATTING */ - /* these are the normal html yahoo sends (besides <font>). - * anything else will get turned into <tag>, so if I forgot - * about something, please add it. Why Yahoo! has to send unescaped - * <'s and >'s that aren't supposed to be html is beyond me. - */ - g_hash_table_insert(ht, "<b>", "<b>"); - g_hash_table_insert(ht, "<i>", "<i>"); - g_hash_table_insert(ht, "<u>", "<u>"); + /* We don't support these tags, so discard them */ + g_hash_table_insert(tags_ht, "alt", ""); + g_hash_table_insert(tags_ht, "fade", ""); + g_hash_table_insert(tags_ht, "snd", ""); + g_hash_table_insert(tags_ht, "/alt", ""); + g_hash_table_insert(tags_ht, "/fade", ""); - g_hash_table_insert(ht, "</b>", "</b>"); - g_hash_table_insert(ht, "</i>", "</i>"); - g_hash_table_insert(ht, "</u>", "</u>"); - g_hash_table_insert(ht, "</font>", "</font>"); + /* Official clients don't seem to send b, i or u tags. They use + * the escape codes listed above. Official clients definitely send + * font tags, though. I wonder if we can remove the opening and + * closing b, i and u tags from here? */ + g_hash_table_insert(tags_ht, "b", "<b>"); + g_hash_table_insert(tags_ht, "i", "<i>"); + g_hash_table_insert(tags_ht, "u", "<u>"); + g_hash_table_insert(tags_ht, "font", "<font>"); + + g_hash_table_insert(tags_ht, "/b", "</b>"); + g_hash_table_insert(tags_ht, "/i", "</i>"); + g_hash_table_insert(tags_ht, "/u", "</u>"); + g_hash_table_insert(tags_ht, "/font", "</font>"); } void yahoo_dest_colorht() { - if (ht == NULL) + if (esc_codes_ht == NULL) /* Hash table has already been destroyed */ return; - g_hash_table_destroy(ht); - ht = NULL; + g_hash_table_destroy(esc_codes_ht); + esc_codes_ht = NULL; + g_hash_table_destroy(tags_ht); + tags_ht = NULL; } +#ifndef USE_CSS_FORMATTING static int point_to_html(int x) { if (x < 9) @@ -312,128 +362,303 @@ return 6; return 7; } +#endif /* !USE_CSS_FORMATTING */ -/* The Yahoo size tag is actually an absz tag; convert it to an HTML size, and include both tags */ -static void _font_tags_fix_size(GString *tag, GString *dest) +static void append_attrs_datalist_foreach_cb(GQuark key_id, gpointer data, gpointer user_data) { - char *x, *end; - int size; + const char *key; + const char *value; + xmlnode *cur; + + key = g_quark_to_string(key_id); + value = data; + cur = user_data; + + xmlnode_set_attrib(cur, key, value); +} - if (((x = strstr(tag->str, "size"))) && ((x = strchr(x, '=')))) { - while (*x && !g_ascii_isdigit(*x)) - x++; - if (*x) { - int htmlsize; +/** + * @param cur A pointer to the position in the XML tree that we're + * currently building. This will be modified when opening a tag + * or closing an existing tag. + */ +static void yahoo_codes_to_html_add_tag(xmlnode **cur, const char *tag, gboolean is_closing_tag, const gchar *tag_name, gboolean is_font_tag) +{ + if (is_closing_tag) { + xmlnode *tmp; + GSList *dangling_tags = NULL; - size = strtol(x, &end, 10); - htmlsize = point_to_html(size); - g_string_append_len(dest, tag->str, x - tag->str); - g_string_append_printf(dest, "%d", htmlsize); - g_string_append_printf(dest, "\" absz=\"%d", size); - g_string_append(dest, end); - } else { - g_string_append(dest, tag->str); + /* Move up the DOM until we find the opening tag */ + for (tmp = *cur; tmp != NULL; tmp = xmlnode_get_parent(tmp)) { + /* Add one to tag_name when doing this comparison because it starts with a / */ + if (g_str_equal(tmp->name, tag_name + 1)) + /* Found */ + break; + dangling_tags = g_slist_prepend(dangling_tags, tmp); + } + if (tmp == NULL) { + /* This is a closing tag with no opening tag. Useless. */ + purple_debug_error("yahoo", "Ignoring unmatched tag %s", tag); + g_slist_free(dangling_tags); return; } + + /* Move our current position up, now that we've closed a tag */ + *cur = xmlnode_get_parent(tmp); + + /* Re-open any tags that were nested below the tag we just closed */ + while (dangling_tags != NULL) { + tmp = dangling_tags->data; + dangling_tags = g_slist_delete_link(dangling_tags, dangling_tags); + + /* Create a copy of this tag+attributes (but not child tags or + * data) at our new location */ + *cur = xmlnode_new_child(*cur, tmp->name); + for (tmp = tmp->child; tmp != NULL; tmp = tmp->next) + if (tmp->type == XMLNODE_TYPE_ATTRIB) + xmlnode_set_attrib_full(*cur, tmp->name, + tmp->xmlns, tmp->prefix, tmp->data); + } } else { - g_string_append(dest, tag->str); - return; + const char *start; + const char *end; + GData *attributes; + char *fontsize = NULL; + + purple_markup_find_tag(tag_name, tag, &start, &end, &attributes); + *cur = xmlnode_new_child(*cur, tag_name); + + if (is_font_tag) { + /* Special case for the font size attribute */ + fontsize = g_strdup(g_datalist_get_data(&attributes, "size")); + if (fontsize != NULL) + g_datalist_remove_data(&attributes, "size"); + } + + /* Add all font tag attributes */ + g_datalist_foreach(&attributes, append_attrs_datalist_foreach_cb, *cur); + g_datalist_clear(&attributes); + + if (fontsize != NULL) { +#ifdef USE_CSS_FORMATTING + /* + * The Yahoo font size value is given in pt, even though the HTML + * standard for <font size="x"> treats the size as a number on a + * scale between 1 and 7. So we insert the font size as a CSS + * style on a span tag. + */ + gchar *tmp = g_strdup_printf("font-size: %spt", fontsize); + *cur = xmlnode_new_child(*cur, "span"); + xmlnode_set_attrib(*cur, "style", tmp); + g_free(tmp); +#else + /* + * The Yahoo font size value is given in pt, even though the HTML + * standard for <font size="x"> treats the size as a number on a + * scale between 1 and 7. So we convert it to an appropriate + * value. This loses precision, which is why CSS formatting is + * preferred. The "absz" attribute remains here for backward + * compatibility with UIs that might use it, but it is totally + * not standard at all. + */ + int size, htmlsize; + gchar tmp[11]; + size = strtol(fontsize, NULL, 10); + htmlsize = point_to_html(size); + sprintf(tmp, "%u", htmlsize); + xmlnode_set_attrib(*cur, "size", tmp); + xmlnode_set_attrib(*cur, "absz", fontsize); +#endif /* !USE_CSS_FORMATTING */ + g_free(fontsize); + } } } +/** + * Similar to purple_markup_get_tag_name(), but works with closing tags. + * + * @return The lowercase name of the tag. If this is a closing tag then + * this value starts with a forward slash. The caller must free + * this string with g_free. + */ +static gchar *yahoo_markup_get_tag_name(const char *tag, gboolean *is_closing_tag) +{ + size_t len; + + *is_closing_tag = (tag[1] == '/'); + if (*is_closing_tag) + len = strcspn(tag + 1, "> "); + else + len = strcspn(tag + 1, "> /"); + + return g_utf8_strdown(tag + 1, len); +} + +/* + * Yahoo! messages generally aren't well-formed. Their markup is + * more of a flow from start to finish rather than a hierarchy from + * outer to inner. They tend to open tags and close them only when + * necessary. + * + * Example: <font size="8">size 8 <font size="16">size 16 <font size="8">size 8 again + * + * But we want to send well-formed HTML to the core, so we step through + * the input string and build an xmlnode tree containing sanitized HTML. + */ char *yahoo_codes_to_html(const char *x) { - GString *s, *tmp; - int i, j, xs, nomoreendtags = 0; /* s/endtags/closinganglebrackets */ - char *match, *ret; + size_t x_len; + xmlnode *html, *cur; + GString *cdata = g_string_new(NULL); + int i, j; + gboolean no_more_gt_brackets = FALSE; + const char *match; + gchar *xmlstr1, *xmlstr2; - s = g_string_sized_new(strlen(x)); + x_len = strlen(x); + html = xmlnode_new("html"); - for (i = 0, xs = strlen(x); i < xs; i++) { + cur = html; + for (i = 0; i < x_len; i++) { if ((x[i] == 0x1b) && (x[i+1] == '[')) { + /* This escape sequence signifies the beginning of some + * text formatting code */ j = i + 1; - while (j++ < xs) { + while (j++ < x_len) { + gchar *code; + if (x[j] != 'm') + /* Keep looking for the end of this sequence */ continue; - else { - tmp = g_string_new_len(x + i + 2, j - i - 2); - if (tmp->str[0] == '#') - g_string_append_printf(s, "<FONT COLOR=\"%s\">", tmp->str); - else if ((match = (char *) g_hash_table_lookup(ht, tmp->str))) - g_string_append(s, match); - else { - purple_debug_error("yahoo", - "Unknown ansi code 'ESC[%sm'.\n", tmp->str); - g_string_free(tmp, TRUE); - break; - } + + /* We've reached the end of the formatting sequence, yay */ + + /* Append any character data that belongs in the current node */ + if (cdata->len > 0) { + xmlnode_insert_data(cur, cdata->str, cdata->len); + g_string_truncate(cdata, 0); + } - i = j; - g_string_free(tmp, TRUE); - break; + code = g_strndup(x + i + 2, j - i - 2); + if (code[0] == '#') { +#ifdef USE_CSS_FORMATTING + gchar *tmp = g_strdup_printf("color: %s", code); + cur = xmlnode_new_child(cur, "span"); + xmlnode_set_attrib(cur, "style", tmp); + g_free(tmp); +#else + cur = xmlnode_new_child(cur, "font"); + xmlnode_set_attrib(cur, "color", code); +#endif /* !USE_CSS_FORMATTING */ + + } else if ((match = g_hash_table_lookup(esc_codes_ht, code))) { + gboolean is_closing_tag; + gchar *tag_name; + + tag_name = yahoo_markup_get_tag_name(match, &is_closing_tag); + yahoo_codes_to_html_add_tag(&cur, match, is_closing_tag, tag_name, FALSE); + g_free(tag_name); + + } else { + purple_debug_error("yahoo", + "Ignoring unknown ansi code 'ESC[%sm'.\n", code); } + + g_free(code); + i = j; + break; } - } else if (!nomoreendtags && (x[i] == '<')) { + } else if (x[i] == '<' && !no_more_gt_brackets) { + /* The start of an HTML tag */ j = i; - while (j++ < xs) { - if (x[j] != '>') - if (j == xs) { - g_string_append(s, "<"); - nomoreendtags = 1; - } - else - continue; - else { - tmp = g_string_new_len(x + i, j - i + 1); - g_string_ascii_down(tmp); + while (j++ < x_len) { + gchar *tag; + gboolean is_closing_tag; + gchar *tag_name; - if ((match = (char *) g_hash_table_lookup(ht, tmp->str))) - g_string_append(s, match); - else if (!strncmp(tmp->str, "<fade ", 6) || - !strncmp(tmp->str, "<alt ", 5) || - !strncmp(tmp->str, "<snd ", 5)) { - - /* remove this if gtkimhtml ever supports any of these */ - i = j; - g_string_free(tmp, TRUE); - break; + if (x[j] != '>') { + if (x[j] == '"') { + /* We're inside a quoted attribute value. Skip to the end */ + j++; + while (j != x_len && x[j] != '"') + j++; + } else if (x[j] == '\'') { + /* We're inside a quoted attribute value. Skip to the end */ + j++; + while (j != x_len && x[j] != '\'') + j++; + } + if (j != x_len) + /* Keep looking for the end of this tag */ + continue; - } else if (!strncmp(tmp->str, "<font ", 6)) { - _font_tags_fix_size(tmp, s); - } else { - g_string_append(s, "<"); - g_string_free(tmp, TRUE); - break; - } - - i = j; - g_string_free(tmp, TRUE); + /* This < has no corresponding > */ + g_string_append_c(cdata, x[i]); + no_more_gt_brackets = TRUE; break; } + tag = g_strndup(x + i, j - i + 1); + tag_name = yahoo_markup_get_tag_name(tag, &is_closing_tag); + + match = g_hash_table_lookup(tags_ht, tag_name); + if (match == NULL) { + /* Unknown tag. The user probably typed a less-than sign */ + g_string_append_c(cdata, x[i]); + no_more_gt_brackets = TRUE; + g_free(tag); + g_free(tag_name); + break; + } + + /* Some tags are in the hash table only because we + * want to ignore them */ + if (match[0] != '\0') { + /* Append any character data that belongs in the current node */ + if (cdata->len > 0) { + xmlnode_insert_data(cur, cdata->str, cdata->len); + g_string_truncate(cdata, 0); + } + if (g_str_equal(tag_name, "font")) + /* Font tags are a special case. We don't + * necessarily want to replace the whole thing-- + * we just want to fix the size attribute. */ + yahoo_codes_to_html_add_tag(&cur, tag, is_closing_tag, tag_name, TRUE); + else + yahoo_codes_to_html_add_tag(&cur, match, is_closing_tag, tag_name, FALSE); + } + + i = j; + g_free(tag); + g_free(tag_name); + break; } } else { - if (x[i] == '<') - g_string_append(s, "<"); - else if (x[i] == '>') - g_string_append(s, ">"); - else if (x[i] == '&') - g_string_append(s, "&"); - else if (x[i] == '"') - g_string_append(s, """); - else - g_string_append_c(s, x[i]); + g_string_append_c(cdata, x[i]); } } - ret = s->str; - g_string_free(s, FALSE); - purple_debug_misc("yahoo", "yahoo_codes_to_html: Returning string: '%s'.\n", ret); - return ret; + /* Append any remaining character data */ + if (cdata->len > 0) + xmlnode_insert_data(cur, cdata->str, cdata->len); + g_string_free(cdata, TRUE); + + /* Serialize our HTML */ + xmlstr1 = xmlnode_to_str(html, NULL); + xmlnode_free(html); + + /* Strip off the outter HTML node */ + /* This probably isn't necessary, especially if we made the outter HTML + * node an empty span. But the HTML is simpler this way. */ + xmlstr2 = g_strndup(xmlstr1 + 6, strlen(xmlstr1) - 13); + g_free(xmlstr1); + + purple_debug_misc("yahoo", "yahoo_codes_to_html: Returning string: '%s'.\n", xmlstr2); + return xmlstr2; } /* borrowed from gtkimhtml */ @@ -441,8 +666,16 @@ #define POINT_SIZE(x) (_point_sizes [MIN ((x > 0 ? x : 1), MAX_FONT_SIZE) - 1]) static const gint _point_sizes [] = { 8, 10, 12, 14, 20, 30, 40 }; -enum fatype { size, color, face, junk }; -typedef struct { +enum fatype +{ + FATYPE_SIZE, + FATYPE_COLOR, + FATYPE_FACE, + FATYPE_JUNK +}; + +typedef struct +{ enum fatype type; union { int size; @@ -454,28 +687,26 @@ static void fontattr_free(fontattr *f) { - if (f->type == color) + if (f->type == FATYPE_COLOR) g_free(f->u.color); - else if (f->type == face) + else if (f->type == FATYPE_FACE) g_free(f->u.face); g_free(f); } -static void yahoo_htc_queue_cleanup(GQueue *q) +static void yahoo_htc_list_cleanup(GSList *l) { - char *tmp; - - while ((tmp = g_queue_pop_tail(q))) - g_free(tmp); - g_queue_free(q); + while (l != NULL) { + g_free(l->data); + l = g_slist_delete_link(l, l); + } } static void _parse_font_tag(const char *src, GString *dest, int *i, int *j, - int len, GQueue *colors, GQueue *tags, GQueue *ftattr) + int len, GSList **colors, GSList **tags, GQueue *ftattr) { - int m, n, vstart; - gboolean quote = 0, done = 0; + gboolean quote = FALSE, done = FALSE; m = *j; @@ -500,7 +731,7 @@ if (src[n] == '"') { if (!quote) { - quote = 1; + quote = TRUE; vstart = n; continue; } else { @@ -509,14 +740,14 @@ } if (!quote && ((src[n] == ' ') || (src[n] == '>'))) - done = 1; + done = TRUE; if (done) { if (!g_ascii_strncasecmp(&src[*j+1], "FACE", m - *j - 1)) { fontattr *f; f = g_new(fontattr, 1); - f->type = face; + f->type = FATYPE_FACE; f->u.face = g_strndup(&src[vstart+1], n-vstart-1); if (!ftattr) ftattr = g_queue_new(); @@ -527,7 +758,7 @@ fontattr *f; f = g_new(fontattr, 1); - f->type = size; + f->type = FATYPE_SIZE; f->u.size = POINT_SIZE(strtol(&src[vstart+1], NULL, 10)); if (!ftattr) ftattr = g_queue_new(); @@ -538,7 +769,7 @@ fontattr *f; f = g_new(fontattr, 1); - f->type = color; + f->type = FATYPE_COLOR; f->u.color = g_strndup(&src[vstart+1], n-vstart-1); if (!ftattr) ftattr = g_queue_new(); @@ -549,7 +780,7 @@ fontattr *f; f = g_new(fontattr, 1); - f->type = junk; + f->type = FATYPE_JUNK; f->u.junk = g_strndup(&src[*j+1], n-*j); if (!ftattr) ftattr = g_queue_new(); @@ -566,56 +797,52 @@ *j = m; if (src[m] == '>') { - gboolean needendtag = 0; + gboolean needendtag = FALSE; fontattr *f; GString *tmp = g_string_new(NULL); - char *colorstr; if (!g_queue_is_empty(ftattr)) { while ((f = g_queue_pop_tail(ftattr))) { switch (f->type) { - case size: + case FATYPE_SIZE: if (!needendtag) { - needendtag = 1; + needendtag = TRUE; g_string_append(dest, "<font "); } g_string_append_printf(dest, "size=\"%d\" ", f->u.size); - fontattr_free(f); break; - case face: + case FATYPE_FACE: if (!needendtag) { - needendtag = 1; + needendtag = TRUE; g_string_append(dest, "<font "); } g_string_append_printf(dest, "face=\"%s\" ", f->u.face); - fontattr_free(f); break; - case junk: + case FATYPE_JUNK: if (!needendtag) { - needendtag = 1; + needendtag = TRUE; g_string_append(dest, "<font "); } g_string_append(dest, f->u.junk); - fontattr_free(f); break; - case color: + case FATYPE_COLOR: if (needendtag) { g_string_append(tmp, "</font>"); dest->str[dest->len-1] = '>'; - needendtag = 0; + needendtag = TRUE; } - colorstr = g_queue_peek_tail(colors); - g_string_append(tmp, colorstr ? colorstr : "\033[#000000m"); + g_string_append(tmp, *colors ? (*colors)->data : "\033[#000000m"); g_string_append_printf(dest, "\033[%sm", f->u.color); - g_queue_push_tail(colors, g_strdup_printf("\033[%sm", f->u.color)); - fontattr_free(f); + *colors = g_slist_prepend(*colors, + g_strdup_printf("\033[%sm", f->u.color)); break; } + fontattr_free(f); } g_queue_free(ftattr); @@ -623,10 +850,10 @@ if (needendtag) { dest->str[dest->len-1] = '>'; - g_queue_push_tail(tags, g_strdup("</font>")); + *tags = g_slist_prepend(*tags, g_strdup("</font>")); g_string_free(tmp, TRUE); } else { - g_queue_push_tail(tags, tmp->str); + *tags = g_slist_prepend(*tags, tmp->str); g_string_free(tmp, FALSE); } } @@ -635,31 +862,31 @@ break; } } - } char *yahoo_html_to_codes(const char *src) { - int i, j, len; + GSList *colors = NULL; + GSList *tags = NULL; + size_t src_len; + int i, j; GString *dest; - char *ret, *esc; - GQueue *colors, *tags; + char *esc; GQueue *ftattr = NULL; gboolean no_more_specials = FALSE; - colors = g_queue_new(); - tags = g_queue_new(); - dest = g_string_sized_new(strlen(src)); + src_len = strlen(src); + dest = g_string_sized_new(src_len); - for (i = 0, len = strlen(src); i < len; i++) { + for (i = 0; i < src_len; i++) { - if (!no_more_specials && src[i] == '<') { + if (src[i] == '<' && !no_more_specials) { j = i; while (1) { j++; - if (j >= len) { /* no '>' */ + if (j >= src_len) { /* no '>' */ g_string_append_c(dest, src[i]); no_more_specials = TRUE; break; @@ -688,7 +915,7 @@ char *t = strchr(&src[j], '>'); if (!t) { g_string_append(dest, &src[i]); - i = len; + i = src_len; break; } else { i = t - src; @@ -697,17 +924,19 @@ } else if (!g_ascii_strncasecmp(&src[i+1], "A HREF=\"", j - i - 1)) { j += 7; g_string_append(dest, "\033[lm"); + if (purple_str_has_prefix(src + j, "mailto:")) + j += sizeof("mailto:") - 1; while (1) { g_string_append_c(dest, src[j]); - if (++j >= len) { - i = len; + if (++j >= src_len) { + i = src_len; break; } if (src[j] == '"') { g_string_append(dest, "\033[xlm"); while (1) { - if (++j >= len) { - i = len; + if (++j >= src_len) { + i = src_len; break; } if (!g_ascii_strncasecmp(&src[j], "</A>", 4)) { @@ -721,9 +950,9 @@ } } else if (!g_ascii_strncasecmp(&src[i+1], "SPAN", j - i - 1)) { /* drop span tags */ while (1) { - if (++j >= len) { + if (++j >= src_len) { g_string_append(dest, &src[i]); - i = len; + i = src_len; break; } if (src[j] == '>') { @@ -733,9 +962,9 @@ } } else if (g_ascii_strncasecmp(&src[i+1], "FONT", j - i - 1)) { /* not interested! */ while (1) { - if (++j >= len) { + if (++j >= src_len) { g_string_append(dest, &src[i]); - i = len; + i = src_len; break; } if (src[j] == '>') { @@ -745,7 +974,7 @@ } } } else { /* yay we have a font tag */ - _parse_font_tag(src, dest, &i, &j, len, colors, tags, ftattr); + _parse_font_tag(src, dest, &i, &j, src_len, &colors, &tags, ftattr); } break; @@ -779,16 +1008,18 @@ /* mmm, </body> tags. *BURP* */ } else if (!g_ascii_strncasecmp(&src[i+1], "/SPAN", sublen)) { /* </span> tags. dangerously close to </spam> */ - } else if (!g_ascii_strncasecmp(&src[i+1], "/FONT", sublen) && g_queue_peek_tail(tags)) { - char *etag, *cl; + } else if (!g_ascii_strncasecmp(&src[i+1], "/FONT", sublen) && tags != NULL) { + char *etag; - etag = g_queue_pop_tail(tags); + etag = tags->data; + tags = g_slist_delete_link(tags, tags); if (etag) { g_string_append(dest, etag); if (!strcmp(etag, "</font>")) { - cl = g_queue_pop_tail(colors); - if (cl) - g_free(cl); + if (colors != NULL) { + g_free(colors->data); + colors = g_slist_delete_link(colors, colors); + } } g_free(etag); } @@ -806,36 +1037,26 @@ } } else { - if (((len - i) >= 4) && !strncmp(&src[i], "<", 4)) { - g_string_append_c(dest, '<'); - i += 3; - } else if (((len - i) >= 4) && !strncmp(&src[i], ">", 4)) { - g_string_append_c(dest, '>'); - i += 3; - } else if (((len - i) >= 5) && !strncmp(&src[i], "&", 5)) { - g_string_append_c(dest, '&'); - i += 4; - } else if (((len - i) >= 6) && !strncmp(&src[i], """, 6)) { - g_string_append_c(dest, '"'); - i += 5; - } else if (((len - i) >= 6) && !strncmp(&src[i], "'", 6)) { - g_string_append_c(dest, '\''); - i += 5; - } else { + const char *entity; + int length; + + entity = purple_markup_unescape_entity(src + i, &length); + if (entity != NULL) { + /* src[i] is the start of an HTML entity */ + g_string_append(dest, entity); + i += length - 1; + } else + /* src[i] is a normal character */ g_string_append_c(dest, src[i]); - } } } - ret = dest->str; - g_string_free(dest, FALSE); - - esc = g_strescape(ret, NULL); + esc = g_strescape(dest->str, NULL); purple_debug_misc("yahoo", "yahoo_html_to_codes: Returning string: '%s'.\n", esc); g_free(esc); - yahoo_htc_queue_cleanup(colors); - yahoo_htc_queue_cleanup(tags); + yahoo_htc_list_cleanup(colors); + yahoo_htc_list_cleanup(tags); - return ret; + return g_string_free(dest, FALSE); }
--- a/libpurple/protocols/yahoo/yahoo_filexfer.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/yahoo/yahoo_filexfer.c Thu Aug 13 17:15:06 2009 +0000 @@ -884,46 +884,48 @@ /* Build the file transfer handle. */ xfer = purple_xfer_new(gc->account, PURPLE_XFER_RECEIVE, from); - if (xfer) - { - xfer->data = xfer_data; + if (xfer == NULL) { + g_free(xfer_data); + g_return_if_reached(); + } + + xfer->data = xfer_data; - /* Set the info about the incoming file. */ - if (filename) { - char *utf8_filename = yahoo_string_decode(gc, filename, TRUE); + /* Set the info about the incoming file. */ + if (filename) { + char *utf8_filename = yahoo_string_decode(gc, filename, TRUE); + purple_xfer_set_filename(xfer, utf8_filename); + g_free(utf8_filename); + } else { + gchar *start, *end; + start = g_strrstr(xfer_data->path, "/"); + if (start) + start++; + end = g_strrstr(xfer_data->path, "?"); + if (start && *start && end) { + char *utf8_filename; + filename = g_strndup(start, end - start); + utf8_filename = yahoo_string_decode(gc, filename, TRUE); + g_free(filename); purple_xfer_set_filename(xfer, utf8_filename); g_free(utf8_filename); - } else { - gchar *start, *end; - start = g_strrstr(xfer_data->path, "/"); - if (start) - start++; - end = g_strrstr(xfer_data->path, "?"); - if (start && *start && end) { - char *utf8_filename; - filename = g_strndup(start, end - start); - utf8_filename = yahoo_string_decode(gc, filename, TRUE); - g_free(filename); - purple_xfer_set_filename(xfer, utf8_filename); - g_free(utf8_filename); - filename = NULL; - } + filename = NULL; } + } - purple_xfer_set_size(xfer, filesize); + purple_xfer_set_size(xfer, filesize); - /* Setup our I/O op functions */ - purple_xfer_set_init_fnc(xfer, yahoo_xfer_init); - purple_xfer_set_start_fnc(xfer, yahoo_xfer_start); - purple_xfer_set_end_fnc(xfer, yahoo_xfer_end); - purple_xfer_set_cancel_send_fnc(xfer, yahoo_xfer_cancel_send); - purple_xfer_set_cancel_recv_fnc(xfer, yahoo_xfer_cancel_recv); - purple_xfer_set_read_fnc(xfer, yahoo_xfer_read); - purple_xfer_set_write_fnc(xfer, yahoo_xfer_write); + /* Setup our I/O op functions */ + purple_xfer_set_init_fnc(xfer, yahoo_xfer_init); + purple_xfer_set_start_fnc(xfer, yahoo_xfer_start); + purple_xfer_set_end_fnc(xfer, yahoo_xfer_end); + purple_xfer_set_cancel_send_fnc(xfer, yahoo_xfer_cancel_send); + purple_xfer_set_cancel_recv_fnc(xfer, yahoo_xfer_cancel_recv); + purple_xfer_set_read_fnc(xfer, yahoo_xfer_read); + purple_xfer_set_write_fnc(xfer, yahoo_xfer_write); - /* Now perform the request */ - purple_xfer_request(xfer); - } + /* Now perform the request */ + purple_xfer_request(xfer); } PurpleXfer *yahoo_new_xfer(PurpleConnection *gc, const char *who) @@ -938,19 +940,22 @@ /* Build the file transfer handle. */ xfer = purple_xfer_new(gc->account, PURPLE_XFER_SEND, who); - if (xfer) + if (xfer == NULL) { - xfer->data = xfer_data; + g_free(xfer_data); + g_return_val_if_reached(NULL); + } + + xfer->data = xfer_data; - /* Setup our I/O op functions */ - purple_xfer_set_init_fnc(xfer, yahoo_xfer_init); - purple_xfer_set_start_fnc(xfer, yahoo_xfer_start); - purple_xfer_set_end_fnc(xfer, yahoo_xfer_end); - purple_xfer_set_cancel_send_fnc(xfer, yahoo_xfer_cancel_send); - purple_xfer_set_cancel_recv_fnc(xfer, yahoo_xfer_cancel_recv); - purple_xfer_set_read_fnc(xfer, yahoo_xfer_read); - purple_xfer_set_write_fnc(xfer, yahoo_xfer_write); - } + /* Setup our I/O op functions */ + purple_xfer_set_init_fnc(xfer, yahoo_xfer_init); + purple_xfer_set_start_fnc(xfer, yahoo_xfer_start); + purple_xfer_set_end_fnc(xfer, yahoo_xfer_end); + purple_xfer_set_cancel_send_fnc(xfer, yahoo_xfer_cancel_send); + purple_xfer_set_cancel_recv_fnc(xfer, yahoo_xfer_cancel_recv); + purple_xfer_set_read_fnc(xfer, yahoo_xfer_read); + purple_xfer_set_write_fnc(xfer, yahoo_xfer_write); return xfer; } @@ -1588,6 +1593,7 @@ char *service = NULL; char *filename = NULL; char *xfer_peer_idstring = NULL; + char *utf8_filename; unsigned long filesize = 0L; GSList *l; GSList *filename_list = NULL; @@ -1714,42 +1720,44 @@ /* Build the file transfer handle. */ xfer = purple_xfer_new(gc->account, PURPLE_XFER_RECEIVE, from); + if (xfer == NULL) + { + g_free(xfer_data); + g_return_if_reached(); + } + xfer->message = NULL; - if (xfer) - { - /* Set the info about the incoming file. */ - char *utf8_filename = yahoo_string_decode(gc, filename, TRUE); - purple_xfer_set_filename(xfer, utf8_filename); - g_free(utf8_filename); - purple_xfer_set_size(xfer, filesize); + /* Set the info about the incoming file. */ + utf8_filename = yahoo_string_decode(gc, filename, TRUE); + purple_xfer_set_filename(xfer, utf8_filename); + g_free(utf8_filename); + purple_xfer_set_size(xfer, filesize); - xfer->data = xfer_data; - + xfer->data = xfer_data; - /* Setup our I/O op functions */ - purple_xfer_set_init_fnc(xfer, yahoo_xfer_init_15); - purple_xfer_set_start_fnc(xfer, yahoo_xfer_start); - purple_xfer_set_end_fnc(xfer, yahoo_xfer_end); - purple_xfer_set_cancel_send_fnc(xfer, yahoo_xfer_cancel_send); - purple_xfer_set_cancel_recv_fnc(xfer, yahoo_xfer_cancel_recv); - purple_xfer_set_read_fnc(xfer, yahoo_xfer_read); - purple_xfer_set_write_fnc(xfer, yahoo_xfer_write); - purple_xfer_set_request_denied_fnc(xfer,yahoo_xfer_cancel_recv); + /* Setup our I/O op functions */ + purple_xfer_set_init_fnc(xfer, yahoo_xfer_init_15); + purple_xfer_set_start_fnc(xfer, yahoo_xfer_start); + purple_xfer_set_end_fnc(xfer, yahoo_xfer_end); + purple_xfer_set_cancel_send_fnc(xfer, yahoo_xfer_cancel_send); + purple_xfer_set_cancel_recv_fnc(xfer, yahoo_xfer_cancel_recv); + purple_xfer_set_read_fnc(xfer, yahoo_xfer_read); + purple_xfer_set_write_fnc(xfer, yahoo_xfer_write); + purple_xfer_set_request_denied_fnc(xfer,yahoo_xfer_cancel_recv); - g_hash_table_insert(yd->xfer_peer_idstring_map, - xfer_data->xfer_peer_idstring, - xfer); + g_hash_table_insert(yd->xfer_peer_idstring_map, + xfer_data->xfer_peer_idstring, + xfer); - if(nooffiles > 1) { - gchar* message; - message = g_strdup_printf(_("%s is trying to send you a group of %d files.\n"), xfer->who, nooffiles); - purple_xfer_conversation_write(xfer, message, FALSE); - g_free(message); - } - /* Now perform the request */ - purple_xfer_request(xfer); + if(nooffiles > 1) { + gchar* message; + message = g_strdup_printf(_("%s is trying to send you a group of %d files.\n"), xfer->who, nooffiles); + purple_xfer_conversation_write(xfer, message, FALSE); + g_free(message); } + /* Now perform the request */ + purple_xfer_request(xfer); } void yahoo_process_filetrans_info_15(PurpleConnection *gc, struct yahoo_packet *pkt)
--- a/libpurple/protocols/yahoo/yahoo_packet.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/yahoo/yahoo_packet.c Thu Aug 13 17:15:06 2009 +0000 @@ -397,7 +397,7 @@ struct yahoo_pair *pair = pkt->hash->data; g_free(pair->value); g_free(pair); - pkt->hash = g_slist_remove(pkt->hash, pair); + pkt->hash = g_slist_delete_link(pkt->hash, pkt->hash); } g_free(pkt); }
--- a/libpurple/protocols/zephyr/ZVariables.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/zephyr/ZVariables.c Thu Aug 13 17:15:06 2009 +0000 @@ -18,27 +18,32 @@ #include <pwd.h> #endif -static int get_localvarfile __P((char *bfr)); +static char *get_localvarfile __P((void)); static char *get_varval __P((char *fn, char *val)); static int varline __P((char *bfr, char *var)); char *ZGetVariable(var) char *var; { - char varfile[128], *ret; + char *varfile, *ret; + + if ((varfile = get_localvarfile()) == NULL) + return ((char *)0); - if (get_localvarfile(varfile)) - return ((char *)0); - - if ((ret = get_varval(varfile, var)) != ZERR_NONE) - return (ret); + if ((ret = get_varval(varfile, var)) != ZERR_NONE) { + g_free(varfile); + return ret; + } #ifdef WIN32 - sprintf(varfile, "C:\\zephyr\\zephyr.var"); + varfile = g_strdup("C:\\zephyr\\zephyr.var"); #else - sprintf(varfile, "%s/zephyr.vars", CONFDIR); + varfile = g_strdup_printf("%s/zephyr.vars", CONFDIR); #endif - return (get_varval(varfile, var)); + ret = get_varval(varfile, var); + g_free(varfile); + + return ret; } Code_t ZSetVariable(var, value) @@ -47,18 +52,20 @@ { int written; FILE *fpin, *fpout; - char varfile[128], varfilebackup[128], varbfr[512]; + char *varfile, *varfilebackup, varbfr[512]; written = 0; - if (get_localvarfile(varfile)) + if ((varfile = get_localvarfile()) == NULL) return (ZERR_INTERNAL); - (void) strcpy(varfilebackup, varfile); - (void) strcat(varfilebackup, ".backup"); + varfilebackup = g_strconcat(varfile, ".backup", NULL); - if (!(fpout = fopen(varfilebackup, "w"))) + if (!(fpout = fopen(varfilebackup, "w"))) { + g_free(varfile); + g_free(varfilebackup); return (errno); + } if ((fpin = fopen(varfile, "r")) != NULL) { while (fgets(varbfr, sizeof varbfr, fpin) != (char *) 0) { if (varbfr[strlen(varbfr)-1] < ' ') @@ -74,10 +81,18 @@ } if (!written) fprintf(fpout, "%s = %s\n", var, value); - if (fclose(fpout) == EOF) - return(EIO); /* can't rely on errno */ - if (rename(varfilebackup, varfile)) + if (fclose(fpout) == EOF) { + g_free(varfilebackup); + g_free(varfile); + return(EIO); /* can't rely on errno */ + } + if (rename(varfilebackup, varfile)) { + g_free(varfilebackup); + g_free(varfile); return (errno); + } + g_free(varfilebackup); + g_free(varfile); return (ZERR_NONE); } @@ -85,16 +100,18 @@ char *var; { FILE *fpin, *fpout; - char varfile[128], varfilebackup[128], varbfr[512]; + char *varfile, *varfilebackup, varbfr[512]; - if (get_localvarfile(varfile)) + if ((varfile = get_localvarfile()) == NULL) return (ZERR_INTERNAL); - (void) strcpy(varfilebackup, varfile); - (void) strcat(varfilebackup, ".backup"); + varfilebackup = g_strconcat(varfile, ".backup", NULL); - if (!(fpout = fopen(varfilebackup, "w"))) + if (!(fpout = fopen(varfilebackup, "w"))) { + g_free(varfile); + g_free(varfilebackup); return (errno); + } if ((fpin = fopen(varfile, "r")) != NULL) { while (fgets(varbfr, sizeof varbfr, fpin) != (char *) 0) { if (varbfr[strlen(varbfr)-1] < ' ') @@ -104,42 +121,45 @@ } (void) fclose(fpin); /* don't care about read close errs */ } - if (fclose(fpout) == EOF) - return(EIO); /* errno isn't reliable */ - if (rename(varfilebackup, varfile)) + if (fclose(fpout) == EOF) { + g_free(varfilebackup); + g_free(varfile); + return(EIO); /* errno isn't reliable */ + } + if (rename(varfilebackup, varfile)) { + g_free(varfilebackup); + g_free(varfile); return (errno); + } + g_free(varfilebackup); + g_free(varfile); return (ZERR_NONE); } -static int get_localvarfile(bfr) - char *bfr; +static char *get_localvarfile(void) { - const char *envptr; + const char *base; #ifndef WIN32 struct passwd *pwd; - envptr = purple_home_dir(); + base = purple_home_dir(); #else - envptr = getenv("HOME"); - if (!envptr) - envptr = getenv("HOMEPATH"); - if (!envptr) - envptr = "C:\\"; + base = getenv("HOME"); + if (!base) + base = getenv("HOMEPATH"); + if (!base) + base = "C:\\"; #endif - if (envptr) - (void) strcpy(bfr, envptr); - else { + if (!base) { #ifndef WIN32 if (!(pwd = getpwuid((int) getuid()))) { fprintf(stderr, "Zephyr internal failure: Can't find your entry in /etc/passwd\n"); - return (1); + return NULL; } - (void) strcpy(bfr, pwd->pw_dir); + base = pwd->pw_dir; #endif } - (void) strcat(bfr, "/"); - (void) strcat(bfr, ".zephyr.vars"); - return (0); + return g_strconcat(base, "/.zephyr.vars", NULL); } static char *get_varval(fn, var)
--- a/libpurple/protocols/zephyr/internal.h Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/protocols/zephyr/internal.h Thu Aug 13 17:15:06 2009 +0000 @@ -23,6 +23,12 @@ #define ETIMEDOUT WSAETIMEDOUT #define EADDRINUSE WSAEADDRINUSE +#else /* !WIN32 */ + +#ifndef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN 4096 +#endif + #endif #ifdef ZEPHYR_USES_HESIOD
--- a/libpurple/proxy.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/proxy.c Thu Aug 13 17:15:06 2009 +0000 @@ -2531,4 +2531,9 @@ purple_proxy_connect_data_disconnect(handles->data, NULL); purple_proxy_connect_data_destroy(handles->data); } + + purple_prefs_disconnect_by_handle(purple_proxy_get_handle()); + + purple_proxy_info_destroy(global_proxy_info); + global_proxy_info = NULL; }
--- a/libpurple/purple.h.in Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/purple.h.in Thu Aug 13 17:15:06 2009 +0000 @@ -65,11 +65,13 @@ #include <idle.h> #include <imgstore.h> #include <log.h> +#include <media.h> +#include <mediamanager.h> #include <mime.h> #include <nat-pmp.h> #include <network.h> +#include <notify.h> #include <ntlm.h> -#include <notify.h> #include <plugin.h> #include <pluginpref.h> #include <pounce.h> @@ -82,16 +84,22 @@ #include <savedstatuses.h> #include <server.h> #include <signals.h> +#include <smiley.h> +#include <sound.h> +#include <sound-theme.h> +#include <sound-theme-loader.h> +#include <sslconn.h> #include <status.h> #include <stringref.h> #include <stun.h> -#include <sound.h> -#include <sslconn.h> +#include <theme.h> +#include <theme-loader.h> +#include <theme-manager.h> #include <upnp.h> #include <util.h> #include <value.h> #include <version.h> +#include <whiteboard.h> #include <xmlnode.h> -#include <whiteboard.h> #endif
--- a/libpurple/savedstatuses.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/savedstatuses.c Thu Aug 13 17:15:06 2009 +0000 @@ -1235,6 +1235,8 @@ void purple_savedstatuses_uninit(void) { + gpointer handle = purple_savedstatuses_get_handle(); + remove_old_transient_statuses(); if (save_timer != 0) @@ -1253,6 +1255,7 @@ g_hash_table_destroy(creation_times); creation_times = NULL; - purple_signals_unregister_by_instance(purple_savedstatuses_get_handle()); + purple_signals_unregister_by_instance(handle); + purple_signals_disconnect_by_handle(handle); }
--- a/libpurple/signals.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/signals.c Thu Aug 13 17:15:06 2009 +0000 @@ -363,8 +363,8 @@ { g_free(handler_data); - signal_data->handlers = g_list_remove(signal_data->handlers, - handler_data); + signal_data->handlers = g_list_delete_link(signal_data->handlers, + l); signal_data->handler_count--; found = TRUE; @@ -398,8 +398,8 @@ g_free(handler_data); signal_data->handler_count--; - signal_data->handlers = g_list_remove(signal_data->handlers, - handler_data); + signal_data->handlers = g_list_delete_link(signal_data->handlers, + l); } } }
--- a/libpurple/status.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/status.c Thu Aug 13 17:15:06 2009 +0000 @@ -1660,7 +1660,7 @@ void purple_status_init(void) { - void *handle = purple_status_get_handle; + void *handle = purple_status_get_handle(); purple_prefs_add_none("/purple/status"); purple_prefs_add_none("/purple/status/scores"); @@ -1714,4 +1714,5 @@ void purple_status_uninit(void) { + purple_prefs_disconnect_by_handle(purple_prefs_get_handle()); }
--- a/libpurple/tests/Makefile.am Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/tests/Makefile.am Thu Aug 13 17:15:06 2009 +0000 @@ -11,6 +11,7 @@ tests.h \ test_cipher.c \ test_jabber_jutil.c \ + test_yahoo_util.c \ test_util.c \ $(top_builddir)/libpurple/util.h @@ -27,6 +28,7 @@ @CHECK_LIBS@ \ $(GLIB_LIBS) \ $(top_builddir)/libpurple/protocols/jabber/libjabber.la \ + $(top_builddir)/libpurple/protocols/yahoo/libymsg.la \ $(top_builddir)/libpurple/libpurple.la endif
--- a/libpurple/tests/check_libpurple.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/tests/check_libpurple.c Thu Aug 13 17:15:06 2009 +0000 @@ -76,6 +76,7 @@ srunner_add_suite(sr, cipher_suite()); srunner_add_suite(sr, jabber_jutil_suite()); + srunner_add_suite(sr, yahoo_util_suite()); srunner_add_suite(sr, util_suite()); /* make this a libpurple "ui" */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/tests/test_yahoo_util.c Thu Aug 13 17:15:06 2009 +0000 @@ -0,0 +1,110 @@ +#include <string.h> + +#include "tests.h" +#include "../protocols/yahoo/libymsg.h" + +static void setup_codes_to_html(void) +{ + yahoo_init_colorht(); +} + +static void teardown_codes_to_html(void) +{ + yahoo_dest_colorht(); +} + +START_TEST(test_codes_to_html) +{ + assert_string_equal_free("plain", + yahoo_codes_to_html("plain")); + assert_string_equal_free("unknown ansi code", + yahoo_codes_to_html("unknown \x1B[12345m ansi code")); + assert_string_equal_free("plain <peanut>", + yahoo_codes_to_html("plain <peanut>")); + assert_string_equal_free("plain <peanut", + yahoo_codes_to_html("plain <peanut")); + assert_string_equal_free("plain> peanut", + yahoo_codes_to_html("plain> peanut")); + + /* bold/italic/underline */ + assert_string_equal_free("<b>bold</b>", + yahoo_codes_to_html("\x1B[1mbold")); + assert_string_equal_free("<i>italic</i>", + yahoo_codes_to_html("\x1B[2mitalic")); + assert_string_equal_free("<u>underline</u>", + yahoo_codes_to_html("\x1B[4munderline")); + assert_string_equal_free("no markup", + yahoo_codes_to_html("no\x1B[x4m markup")); + assert_string_equal_free("<b>bold</b> <i>italic</i> <u>underline</u>", + yahoo_codes_to_html("\x1B[1mbold\x1B[x1m \x1B[2mitalic\x1B[x2m \x1B[4munderline")); + assert_string_equal_free("<b>bold <i>bolditalic</i></b><i> italic</i>", + yahoo_codes_to_html("\x1B[1mbold \x1B[2mbolditalic\x1B[x1m italic")); + assert_string_equal_free("<b>bold <i>bolditalic</i></b><i> <u>italicunderline</u></i>", + yahoo_codes_to_html("\x1B[1mbold \x1B[2mbolditalic\x1B[x1m \x1B[4mitalicunderline")); + assert_string_equal_free("<b>bold <i>bolditalic <u>bolditalicunderline</u></i><u> boldunderline</u></b>", + yahoo_codes_to_html("\x1B[1mbold \x1B[2mbolditalic \x1B[4mbolditalicunderline\x1B[x2m boldunderline")); + assert_string_equal_free("<b>bold <i>bolditalic <u>bolditalicunderline</u></i></b><i><u> italicunderline</u></i>", + yahoo_codes_to_html("\x1B[1mbold \x1B[2mbolditalic \x1B[4mbolditalicunderline\x1B[x1m italicunderline")); + +#ifdef USE_CSS_FORMATTING + /* font color */ + assert_string_equal_free("<span style='color: #0000FF'>blue</span>", + yahoo_codes_to_html("\x1B[31mblue")); + assert_string_equal_free("<span style='color: #70ea15'>custom color</span>", + yahoo_codes_to_html("\x1B[#70ea15mcustom color")); + + /* font face */ + assert_string_equal_free("<font face='Georgia'>test</font>", + yahoo_codes_to_html("<font face='Georgia'>test</font>")); + + /* font size */ + assert_string_equal_free("<font><span style='font-size: 15pt'>test</span></font>", + yahoo_codes_to_html("<font size='15'>test")); + assert_string_equal_free("<font><span style='font-size: 32pt'>size 32</span></font>", + yahoo_codes_to_html("<font size='32'>size 32")); + + /* combinations */ + assert_string_equal_free("<font face='Georgia'><span style='font-size: 32pt'>test</span></font>", + yahoo_codes_to_html("<font face='Georgia' size='32'>test")); + assert_string_equal_free("<span style='color: #FF0080'><font><span style='font-size: 15pt'>test</span></font></span>", + yahoo_codes_to_html("\x1B[35m<font size='15'>test")); +#else + /* font color */ + assert_string_equal_free("<font color='#0000FF'>blue</font>", + yahoo_codes_to_html("\x1B[31mblue")); + assert_string_equal_free("<font color='#70ea15'>custom color</font>", + yahoo_codes_to_html("\x1B[#70ea15mcustom color")); + assert_string_equal_free("test", + yahoo_codes_to_html("<ALT #ff0000,#00ff00,#0000ff>test</ALT>")); + + /* font face */ + assert_string_equal_free("<font face='Georgia'>test</font>", + yahoo_codes_to_html("<font face='Georgia'>test")); + + /* font size */ + assert_string_equal_free("<font size='4' absz='15'>test</font>", + yahoo_codes_to_html("<font size='15'>test")); + assert_string_equal_free("<font size='6' absz='32'>size 32</font>", + yahoo_codes_to_html("<font size='32'>size 32")); + + /* combinations */ + assert_string_equal_free("<font face='Georgia' size='6' absz='32'>test</font>", + yahoo_codes_to_html("<font face='Georgia' size='32'>test")); + assert_string_equal_free("<font color='#FF0080'><font size='4' absz='15'>test</font></font>", + yahoo_codes_to_html("\x1B[35m<font size='15'>test")); +#endif /* !USE_CSS_FORMATTING */ +} +END_TEST + +Suite * +yahoo_util_suite(void) +{ + Suite *s = suite_create("Yahoo Utility Functions"); + + TCase *tc = tcase_create("Convert to Numeric"); + tcase_add_unchecked_fixture(tc, setup_codes_to_html, teardown_codes_to_html); + tcase_add_test(tc, test_codes_to_html); + suite_add_tcase(s, tc); + + return s; +}
--- a/libpurple/tests/tests.h Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/tests/tests.h Thu Aug 13 17:15:06 2009 +0000 @@ -10,6 +10,7 @@ Suite * master_suite(void); Suite * cipher_suite(void); Suite * jabber_jutil_suite(void); +Suite * yahoo_util_suite(void); Suite * util_suite(void); /* helper macros */
--- a/libpurple/util.c Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/util.c Thu Aug 13 17:15:06 2009 +0000 @@ -3519,6 +3519,7 @@ void purple_got_protocol_handler_uri(const char *uri) { char proto[11]; + char delimiter; const char *tmp, *param_string; char *cmd; GHashTable *params = NULL; @@ -3534,7 +3535,13 @@ proto[len] = '\0'; tmp++; - purple_debug_info("util", "Processing message '%s' for protocol '%s'.\n", tmp, proto); + + if (g_str_equal(proto, "xmpp")) + delimiter = ';'; + else + delimiter = '&'; + + purple_debug_info("util", "Processing message '%s' for protocol '%s' using delimiter '%c'.\n", tmp, proto, delimiter); if ((param_string = strchr(tmp, '?'))) { const char *keyend = NULL, *pairstart; @@ -3547,7 +3554,7 @@ pairstart = tmp = param_string; while (*tmp || *pairstart) { - if (*tmp == '&' || !(*tmp)) { + if (*tmp == delimiter || !(*tmp)) { /* If there is no explicit value */ if (keyend == NULL) keyend = tmp;
--- a/libpurple/xmlnode.h Tue Jul 28 20:51:56 2009 +0000 +++ b/libpurple/xmlnode.h Thu Aug 13 17:15:06 2009 +0000 @@ -53,10 +53,10 @@ XMLNodeType type; /**< The type of the node. */ char *data; /**< The data for the node. */ size_t data_sz; /**< The size of the data. */ - struct _xmlnode *parent; /**< The parent node or @c NULL.*/ - struct _xmlnode *child; /**< The child node or @c NULL.*/ - struct _xmlnode *lastchild; /**< The last child node or @c NULL.*/ - struct _xmlnode *next; /**< The next node or @c NULL. */ + xmlnode *parent; /**< The parent node or @c NULL.*/ + xmlnode *child; /**< The child node or @c NULL.*/ + xmlnode *lastchild; /**< The last child node or @c NULL.*/ + xmlnode *next; /**< The next node or @c NULL. */ char *prefix; /**< The namespace prefix if any. */ GHashTable *namespace_map; /**< The namespace map. */ };
--- a/pidgin.spec.in Tue Jul 28 20:51:56 2009 +0000 +++ b/pidgin.spec.in Thu Aug 13 17:15:06 2009 +0000 @@ -251,6 +251,7 @@ rm -f $RPM_BUILD_ROOT%{_libdir}/purple-2/*.la rm -f $RPM_BUILD_ROOT%{_libdir}/purple-2/liboscar.so rm -f $RPM_BUILD_ROOT%{_libdir}/purple-2/libjabber.so +rm -f $RPM_BUILD_ROOT%{_libdir}/purple-2/libymsg.so rm -f $RPM_BUILD_ROOT%{_libdir}/*.la rm -f $RPM_BUILD_ROOT%{perl_archlib}/perllocal.pod find $RPM_BUILD_ROOT -type f -name '*.a' -exec rm -f {} ';'
--- a/pidgin/gtkblist.c Tue Jul 28 20:51:56 2009 +0000 +++ b/pidgin/gtkblist.c Thu Aug 13 17:15:06 2009 +0000 @@ -3560,7 +3560,7 @@ } g_free(pce); - cur = g_list_remove(cur, pce); + cur = g_list_delete_link(cur, cur); } } else if (PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_BUDDY(node))
--- a/pidgin/gtkconv.c Tue Jul 28 20:51:56 2009 +0000 +++ b/pidgin/gtkconv.c Thu Aug 13 17:15:06 2009 +0000 @@ -3326,6 +3326,62 @@ } static void +regenerate_media_items(PidginWindow *win) +{ +#ifdef USE_VV + PurpleAccount *account; + PurpleConversation *conv; + + conv = pidgin_conv_window_get_active_conversation(win); + + if (conv == NULL) { + purple_debug_error("gtkconv", "couldn't get active conversation" + " when regenerating media items\n"); + return; + } + + account = purple_conversation_get_account(conv); + + if (account == NULL) { + purple_debug_error("gtkconv", "couldn't get account when" + " regenerating media items\n"); + return; + } + + /* + * Check if account support voice and/or calls, and + * if the current buddy supports it. + */ + if (account != NULL && purple_conversation_get_type(conv) + == PURPLE_CONV_TYPE_IM) { + PurpleMediaCaps caps = + purple_prpl_get_media_caps(account, + purple_conversation_get_name(conv)); + + gtk_widget_set_sensitive(win->audio_call, + caps & PURPLE_MEDIA_CAPS_AUDIO + ? TRUE : FALSE); + gtk_widget_set_sensitive(win->video_call, + caps & PURPLE_MEDIA_CAPS_VIDEO + ? TRUE : FALSE); + gtk_widget_set_sensitive(win->audio_video_call, + caps & PURPLE_MEDIA_CAPS_AUDIO_VIDEO + ? TRUE : FALSE); + } else if (purple_conversation_get_type(conv) + == PURPLE_CONV_TYPE_CHAT) { + /* for now, don't care about chats... */ + gtk_widget_set_sensitive(win->audio_call, FALSE); + gtk_widget_set_sensitive(win->video_call, FALSE); + gtk_widget_set_sensitive(win->audio_video_call, FALSE); + } else { + gtk_widget_set_sensitive(win->audio_call, FALSE); + gtk_widget_set_sensitive(win->video_call, FALSE); + gtk_widget_set_sensitive(win->audio_video_call, FALSE); + } +#endif +} + +static void regenerate_options_items(PidginWindow *win) { GtkWidget *menu; @@ -3410,6 +3466,7 @@ static void menubar_activated(GtkWidget *item, gpointer data) { PidginWindow *win = data; + regenerate_media_items(win); regenerate_options_items(win); regenerate_plugins_items(win); @@ -4308,7 +4365,11 @@ return; } - gtk_entry_set_text(GTK_ENTRY(gtkchat->topic_text), current_topic); + if (current_topic) + gtk_entry_set_text(GTK_ENTRY(gtkchat->topic_text), current_topic); + else + gtk_entry_set_text(GTK_ENTRY(gtkchat->topic_text), ""); + prpl_info->set_chat_topic(gc, purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv)), new_topic); @@ -5376,6 +5437,7 @@ { PurpleConversationUiOps *ui_ops = pidgin_conversations_get_conv_ui_ops(); gboolean hide = FALSE; + guint timer; /* create hidden conv if hide_new pref is always */ if (strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/im/hide_new"), "always") == 0) @@ -5399,6 +5461,13 @@ purple_conversation_new(PURPLE_CONV_TYPE_IM, account, sender); ui_ops->create_conversation = pidgin_conv_new; } + + /* Somebody wants to keep this conversation around, so don't time it out */ + timer = GPOINTER_TO_INT(purple_conversation_get_data(conv, "close-timer")); + if (timer) { + purple_timeout_remove(timer); + purple_conversation_set_data(conv, "close-timer", GINT_TO_POINTER(0)); + } } static void @@ -6484,36 +6553,6 @@ buttons |= GTK_IMHTML_CUSTOM_SMILEY; else buttons &= ~GTK_IMHTML_CUSTOM_SMILEY; - -#ifdef USE_VV - /* check if account support voice calls, and if the current buddy - supports it */ - if (account != NULL && purple_conversation_get_type(conv) - == PURPLE_CONV_TYPE_IM) { - PurpleMediaCaps caps = - purple_prpl_get_media_caps(account, - purple_conversation_get_name(conv)); - - gtk_widget_set_sensitive(win->audio_call, - caps & PURPLE_MEDIA_CAPS_AUDIO - ? TRUE : FALSE); - gtk_widget_set_sensitive(win->video_call, - caps & PURPLE_MEDIA_CAPS_VIDEO - ? TRUE : FALSE); - gtk_widget_set_sensitive(win->audio_video_call, - caps & PURPLE_MEDIA_CAPS_AUDIO_VIDEO - ? TRUE : FALSE); - } else if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) { - /* for now, don't care about chats... */ - gtk_widget_set_sensitive(win->audio_call, FALSE); - gtk_widget_set_sensitive(win->video_call, FALSE); - gtk_widget_set_sensitive(win->audio_video_call, FALSE); - } else { - gtk_widget_set_sensitive(win->audio_call, FALSE); - gtk_widget_set_sensitive(win->video_call, FALSE); - gtk_widget_set_sensitive(win->audio_video_call, FALSE); - } -#endif gtk_imhtml_set_format_functions(GTK_IMHTML(gtkconv->entry), buttons); if (account != NULL)
--- a/pidgin/gtkdialogs.c Tue Jul 28 20:51:56 2009 +0000 +++ b/pidgin/gtkdialogs.c Thu Aug 13 17:15:06 2009 +0000 @@ -170,8 +170,9 @@ {N_("Esperanto"), "eo", "Stéphane Fillod", "fillods@users.sourceforge.net"}, {N_("Spanish"), "es", "Javier Fernández-Sanguino Peña", "jfs@debian.org"}, {N_("Estonian"), "et", "Ivar Smolin", "okul@linux.ee"}, + {N_("Euskera(Basque)"), "eu", "Mikel Pascual Aldabaldetreku", "mikel.paskual@gmail.com"}, + {N_("Euskera(Basque)"), "eu", "Iñaki Larrañaga Murgoitio", "dooteo@zundan.com"}, {N_("Euskera(Basque)"), "eu", "Hizkuntza Politikarako Sailburuordetza", "hizkpol@ej-gv.es"}, - {N_("Euskera(Basque)"), "eu", "Iñaki Larrañaga Murgoitio", "dooteo@zundan.com"}, {N_("Persian"), "fa", "Elnaz Sarbar", "elnaz@farsiweb.info"}, {N_("Persian"), "fa", "Meelad Zakaria", "meelad@farsiweb.info"}, {N_("Persian"), "fa", "Roozbeh Pournader ", "roozbeh@farsiweb.info"}, @@ -213,6 +214,7 @@ {N_("Portuguese-Brazil"), "pt_BR", "Rodrigo Luiz Marques Flores", "rodrigomarquesflores@gmail.com"}, {N_("Pashto"), "ps", "Kashif Masood", "masudmails@yahoo.com"}, {N_("Romanian"), "ro", "Mişu Moldovan", "dumol@gnome.ro"}, + {N_("Romanian"), "ro", "Andrei Popescu", "andreimpopescu@gmail.com"}, {N_("Russian"), "ru", "Антон Самохвалов", "samant.ua@mail.ru"}, {N_("Slovak"), "sk", "Jozef Káčer", "quickparser@gmail.com"}, {N_("Slovak"), "sk", "loptosko", "loptosko@gmail.com"},
--- a/pidgin/gtkimhtml.c Tue Jul 28 20:51:56 2009 +0000 +++ b/pidgin/gtkimhtml.c Thu Aug 13 17:15:06 2009 +0000 @@ -2953,7 +2953,7 @@ font->size = oldfont->size; else font->size = 3; - if ((imhtml->format_functions & (GTK_IMHTML_GROW|GTK_IMHTML_SHRINK))) + if ((imhtml->format_functions & (GTK_IMHTML_GROW|GTK_IMHTML_SHRINK)) && (font->size != 3 || (oldfont && oldfont->size == 3))) gtk_imhtml_font_set_size(imhtml, font->size); g_free(size); fonts = g_slist_prepend (fonts, font); @@ -5838,9 +5838,9 @@ if (activate) { return FALSE; } + klass->protocols = g_list_remove(klass->protocols, proto); g_free(proto->name); g_free(proto); - klass->protocols = g_list_remove(klass->protocols, proto); return TRUE; } else if (!activate) { return FALSE;
--- a/pidgin/gtkmedia.c Tue Jul 28 20:51:56 2009 +0000 +++ b/pidgin/gtkmedia.c Thu Aug 13 17:15:06 2009 +0000 @@ -82,14 +82,14 @@ { PurpleMedia *media; gchar *screenname; - GstElement *send_level; - GstElement *recv_level; + gulong level_handler_id; GtkItemFactory *item_factory; GtkWidget *menubar; GtkWidget *statusbar; GtkWidget *mute; + GtkWidget *pause; GtkWidget *send_progress; GtkWidget *recv_progress; @@ -99,6 +99,7 @@ GtkWidget *display; GtkWidget *send_widget; GtkWidget *recv_widget; + GtkWidget *button_widget; GtkWidget *local_video; GtkWidget *remote_video; @@ -129,9 +130,7 @@ enum { PROP_0, PROP_MEDIA, - PROP_SCREENNAME, - PROP_SEND_LEVEL, - PROP_RECV_LEVEL + PROP_SCREENNAME }; static GType @@ -182,18 +181,6 @@ "The screenname of the user this session is with.", NULL, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); - g_object_class_install_property(gobject_class, PROP_SEND_LEVEL, - g_param_spec_object("send-level", - "Send level", - "The GstElement of this media's send 'level'", - GST_TYPE_ELEMENT, - G_PARAM_READWRITE)); - g_object_class_install_property(gobject_class, PROP_RECV_LEVEL, - g_param_spec_object("recv-level", - "Receive level", - "The GstElement of this media's recv 'level'", - GST_TYPE_ELEMENT, - G_PARAM_READWRITE)); g_type_class_add_private(klass, sizeof(PidginMediaPrivate)); } @@ -207,6 +194,15 @@ NULL, NULL, TRUE); } +static void +pidgin_media_pause_toggled(GtkToggleButton *toggle, PidginMedia *media) +{ + purple_media_stream_info(media->priv->media, + gtk_toggle_button_get_active(toggle) ? + PURPLE_MEDIA_INFO_PAUSE : PURPLE_MEDIA_INFO_UNPAUSE, + NULL, NULL, TRUE); +} + static gboolean pidgin_media_delete_event_cb(GtkWidget *widget, GdkEvent *event, PidginMedia *media) @@ -338,45 +334,16 @@ G_CALLBACK(pidgin_media_delete_event_cb), media); } -static gboolean -level_message_cb(GstBus *bus, GstMessage *message, PidginMedia *gtkmedia) +static void +level_message_cb(PurpleMedia *media, gchar *session_id, gchar *participant, + double level, PidginMedia *gtkmedia) { - gdouble rms_db; - gdouble percent; - const GValue *list; - const GValue *value; - - GstElement *src = GST_ELEMENT(GST_MESSAGE_SRC(message)); GtkWidget *progress; - - if (message->type != GST_MESSAGE_ELEMENT) - return TRUE; - - if (!gst_structure_has_name( - gst_message_get_structure(message), "level")) - return TRUE; - - if (src == gtkmedia->priv->send_level) + if (participant == NULL) progress = gtkmedia->priv->send_progress; - else if (src == gtkmedia->priv->recv_level) + else progress = gtkmedia->priv->recv_progress; - else - return TRUE; - - list = gst_structure_get_value( - gst_message_get_structure(message), "rms"); - - /* Only bother with the first channel. */ - value = gst_value_list_get_value(list, 0); - rms_db = g_value_get_double(value); - - percent = pow(10, rms_db / 20) * 5; - - if(percent > 1.0) - percent = 1.0; - - gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), percent); - return TRUE; + gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), level); } @@ -412,16 +379,6 @@ gtkmedia->priv->item_factory = NULL; } - if (gtkmedia->priv->send_level) { - gst_object_unref(gtkmedia->priv->send_level); - gtkmedia->priv->send_level = NULL; - } - - if (gtkmedia->priv->recv_level) { - gst_object_unref(gtkmedia->priv->recv_level); - gtkmedia->priv->recv_level = NULL; - } - G_OBJECT_CLASS(parent_class)->dispose(media); } @@ -501,17 +458,6 @@ } static void -pidgin_media_accepted_cb(PurpleMedia *media, const gchar *session_id, - const gchar *participant, PidginMedia *gtkmedia) -{ - pidgin_media_set_state(gtkmedia, PIDGIN_MEDIA_ACCEPTED); - pidgin_media_emit_message(gtkmedia, _("Call in progress.")); - gtk_statusbar_push(GTK_STATUSBAR(gtkmedia->priv->statusbar), - 0, _("Call in progress.")); - gtk_widget_show(GTK_WIDGET(gtkmedia)); -} - -static void pidgin_media_accept_cb(PurpleMedia *media, int index) { purple_media_stream_info(media, PURPLE_MEDIA_INFO_ACCEPT, @@ -554,7 +500,7 @@ gtkmedia->priv->request_type = PURPLE_MEDIA_NONE; - purple_request_accept_cancel(gtkmedia, "Media invitation", + purple_request_accept_cancel(gtkmedia, _("Incoming Call"), message, NULL, PURPLE_DEFAULT_ACTION_NONE, (void*)account, gtkmedia->priv->screenname, NULL, gtkmedia->priv->media, @@ -576,8 +522,7 @@ { double val = (double)gtk_range_get_value(GTK_RANGE(range)); #endif - purple_prefs_set_int("/pidgin/media/audio/volume/input", val); - purple_media_set_input_volume(media, NULL, val / 10.0); + purple_media_set_input_volume(media, NULL, val); } static void @@ -591,8 +536,7 @@ { double val = (double)gtk_range_get_value(GTK_RANGE(range)); #endif - purple_prefs_set_int("/pidgin/media/audio/volume/output", val); - purple_media_set_output_volume(media, NULL, NULL, val / 10.0); + purple_media_set_output_volume(media, NULL, NULL, val);