2 * purple - Jabber Protocol Plugin
4 * Purple is the legal property of its developers, whose names are too numerous
5 * to list here. Please refer to the COPYRIGHT file distributed with this
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
26 #include "conversation.h"
37 #include "google/google.h"
38 #include "google/google_presence.h"
42 #include "adhoccommands.h"
47 static GHashTable
*presence_handlers
= NULL
;
51 JabberPresenceType type
;
52 } jabber_presence_types
[] = {
53 { "error", JABBER_PRESENCE_ERROR
},
54 { "probe", JABBER_PRESENCE_PROBE
},
55 { "unavailable", JABBER_PRESENCE_UNAVAILABLE
},
56 { "subscribe", JABBER_PRESENCE_SUBSCRIBE
},
57 { "subscribed", JABBER_PRESENCE_SUBSCRIBED
},
58 { "unsubscribe", JABBER_PRESENCE_UNSUBSCRIBE
},
59 { "unsubscribed", JABBER_PRESENCE_UNSUBSCRIBED
}
60 /* { NULL, JABBER_PRESENCE_AVAILABLE } the default */
63 static JabberPresenceType
64 str_to_presence_type(const char *type
)
69 return JABBER_PRESENCE_AVAILABLE
;
71 for (i
= 0; i
< G_N_ELEMENTS(jabber_presence_types
); ++i
)
72 if (g_str_equal(type
, jabber_presence_types
[i
].name
))
73 return jabber_presence_types
[i
].type
;
75 purple_debug_warning("jabber", "Unknown presence type '%s'\n", type
);
76 return JABBER_PRESENCE_AVAILABLE
;
79 static void chats_send_presence_foreach(gpointer key
, gpointer val
,
82 JabberChat
*chat
= val
;
83 PurpleXmlNode
*presence
= user_data
;
86 if(!chat
->conv
|| chat
->left
)
89 chat_full_jid
= g_strdup_printf("%s@%s/%s", chat
->room
, chat
->server
,
92 purple_xmlnode_set_attrib(presence
, "to", chat_full_jid
);
93 jabber_send(chat
->js
, presence
);
94 g_free(chat_full_jid
);
97 void jabber_presence_fake_to_self(JabberStream
*js
, PurpleStatus
*status
)
99 PurpleAccount
*account
;
100 PurplePresence
*presence
;
102 JabberBuddyResource
*jbr
;
103 const char *username
;
104 JabberBuddyState state
;
108 g_return_if_fail(js
->user
!= NULL
);
110 account
= purple_connection_get_account(js
->gc
);
111 username
= purple_connection_get_display_name(js
->gc
);
112 presence
= purple_account_get_presence(account
);
114 status
= purple_presence_get_active_status(presence
);
115 purple_status_to_jabber(status
, &state
, &msg
, &priority
);
119 if (state
== JABBER_BUDDY_STATE_UNAVAILABLE
||
120 state
== JABBER_BUDDY_STATE_UNKNOWN
) {
121 jabber_buddy_remove_resource(jb
, js
->user
->resource
);
123 jbr
= jabber_buddy_track_resource(jb
, js
->user
->resource
, priority
,
125 jbr
->idle
= purple_presence_is_idle(presence
) ?
126 purple_presence_get_idle_time(presence
) : 0;
130 * While we need to track the status of this resource, the core
131 * only cares if we're on our own buddy list.
133 if (purple_blist_find_buddy(account
, username
)) {
134 jbr
= jabber_buddy_find_resource(jb
, NULL
);
136 purple_protocol_got_user_status(account
, username
,
137 jabber_buddy_state_get_status_id(jbr
->state
),
138 "priority", jbr
->priority
,
139 jbr
->status
? "message" : NULL
, jbr
->status
,
141 purple_protocol_got_user_idle(account
, username
, jbr
->idle
, jbr
->idle
);
143 purple_protocol_got_user_status(account
, username
, "offline",
144 msg
? "message" : NULL
, msg
,
151 void jabber_set_status(PurpleAccount
*account
, PurpleStatus
*status
)
153 PurpleConnection
*gc
;
156 if (!purple_account_is_connected(account
))
159 if (purple_status_is_exclusive(status
) && !purple_status_is_active(status
)) {
160 /* An exclusive status can't be deactivated. You should just
161 * activate some other exclusive status. */
165 gc
= purple_account_get_connection(account
);
166 js
= purple_connection_get_protocol_data(gc
);
168 /* it's a mood update */
169 if (purple_status_type_get_primitive(purple_status_get_status_type(status
)) == PURPLE_STATUS_MOOD
) {
171 purple_status_get_attr_string(status
, PURPLE_MOOD_NAME
);
172 const char *mood_text
=
173 purple_status_get_attr_string(status
, PURPLE_MOOD_COMMENT
);
174 jabber_mood_set(js
, mood
, mood_text
);
178 jabber_presence_send(js
, FALSE
);
181 void jabber_presence_send(JabberStream
*js
, gboolean force
)
183 PurpleAccount
*account
;
184 PurpleXmlNode
*presence
, *x
, *photo
;
185 char *stripped
= NULL
;
186 JabberBuddyState state
;
188 const char *artist
= NULL
, *title
= NULL
, *source
= NULL
, *uri
= NULL
, *track
= NULL
;
192 PurpleStatus
*status
, *tune
;
194 account
= purple_connection_get_account(js
->gc
);
195 p
= purple_account_get_presence(account
);
196 status
= purple_presence_get_active_status(p
);
198 /* we don't want to send presence before we've gotten our roster */
199 if (js
->state
!= JABBER_STREAM_CONNECTED
) {
200 purple_debug_misc("jabber", "attempt to send presence before roster retrieved\n");
204 purple_status_to_jabber(status
, &state
, &stripped
, &priority
);
206 /* check for buzz support */
207 allowBuzz
= purple_status_get_attr_boolean(status
,"buzz");
208 /* changing the buzz state has to trigger a re-broadcasting of the presence for caps */
210 tune
= purple_presence_get_status(p
, "tune");
211 if (js
->googletalk
&& !stripped
&& purple_status_is_active(tune
)) {
212 stripped
= jabber_google_presence_outgoing(tune
);
215 #define CHANGED(a,b) ((!a && b) || (a && a[0] == '\0' && b && b[0] != '\0') || \
216 (a && !b) || (a && a[0] != '\0' && b && b[0] == '\0') || (a && b && strcmp(a,b)))
217 /* check if there are any differences to the <presence> and send them in that case */
218 if (force
|| allowBuzz
!= js
->allowBuzz
|| js
->old_state
!= state
||
219 CHANGED(js
->old_msg
, stripped
) || js
->old_priority
!= priority
||
220 CHANGED(js
->old_avatarhash
, js
->avatar_hash
) || js
->old_idle
!= js
->idle
) {
221 /* Need to update allowBuzz before creating the presence (with caps) */
222 js
->allowBuzz
= allowBuzz
;
224 presence
= jabber_presence_create_js(js
, state
, stripped
, priority
);
226 /* Per XEP-0153 4.1, we must always send the <x> */
227 x
= purple_xmlnode_new_child(presence
, "x");
228 purple_xmlnode_set_namespace(x
, "vcard-temp:x:update");
230 * FIXME: Per XEP-0153 4.3.2 bullet 2, we must not publish our
231 * image hash if another resource has logged in and updated the
232 * vcard avatar. Requires changes in jabber_presence_parse.
234 if (js
->vcard_fetched
) {
235 /* Always publish a <photo>; it's empty if we have no image. */
236 photo
= purple_xmlnode_new_child(x
, "photo");
238 purple_xmlnode_insert_data(photo
, js
->avatar_hash
, -1);
241 jabber_send(js
, presence
);
243 g_hash_table_foreach(js
->chats
, chats_send_presence_foreach
, presence
);
244 purple_xmlnode_free(presence
);
246 /* update old values */
249 g_free(js
->old_avatarhash
);
250 js
->old_msg
= g_strdup(stripped
);
251 js
->old_avatarhash
= g_strdup(js
->avatar_hash
);
252 js
->old_state
= state
;
253 js
->old_priority
= priority
;
254 js
->old_idle
= js
->idle
;
258 /* next, check if there are any changes to the tune values */
259 if (purple_status_is_active(tune
)) {
260 artist
= purple_status_get_attr_string(tune
, PURPLE_TUNE_ARTIST
);
261 title
= purple_status_get_attr_string(tune
, PURPLE_TUNE_TITLE
);
262 source
= purple_status_get_attr_string(tune
, PURPLE_TUNE_ALBUM
);
263 uri
= purple_status_get_attr_string(tune
, PURPLE_TUNE_URL
);
264 track
= purple_status_get_attr_string(tune
, PURPLE_TUNE_TRACK
);
265 length
= (!purple_status_get_attr_value(tune
, PURPLE_TUNE_TIME
)) ? -1 :
266 purple_status_get_attr_int(tune
, PURPLE_TUNE_TIME
);
269 if(CHANGED(artist
, js
->old_artist
) || CHANGED(title
, js
->old_title
) || CHANGED(source
, js
->old_source
) ||
270 CHANGED(uri
, js
->old_uri
) || CHANGED(track
, js
->old_track
) || (length
!= js
->old_length
)) {
271 PurpleJabberTuneInfo tuneinfo
= {
279 jabber_tune_set(js
->gc
, &tuneinfo
);
281 /* update old values */
282 g_free(js
->old_artist
);
283 g_free(js
->old_title
);
284 g_free(js
->old_source
);
286 g_free(js
->old_track
);
287 js
->old_artist
= g_strdup(artist
);
288 js
->old_title
= g_strdup(title
);
289 js
->old_source
= g_strdup(source
);
290 js
->old_uri
= g_strdup(uri
);
291 js
->old_length
= length
;
292 js
->old_track
= g_strdup(track
);
297 jabber_presence_fake_to_self(js
, status
);
300 PurpleXmlNode
*jabber_presence_create_js(JabberStream
*js
, JabberBuddyState state
, const char *msg
, int priority
)
302 PurpleXmlNode
*show
, *status
, *presence
, *pri
, *c
;
303 const char *show_string
= NULL
;
305 gboolean audio_enabled
, video_enabled
;
308 g_return_val_if_fail(js
!=NULL
, NULL
);
310 presence
= purple_xmlnode_new("presence");
312 if(state
== JABBER_BUDDY_STATE_UNAVAILABLE
)
313 purple_xmlnode_set_attrib(presence
, "type", "unavailable");
314 else if(state
!= JABBER_BUDDY_STATE_ONLINE
&&
315 state
!= JABBER_BUDDY_STATE_UNKNOWN
&&
316 state
!= JABBER_BUDDY_STATE_ERROR
)
317 show_string
= jabber_buddy_state_get_show(state
);
320 show
= purple_xmlnode_new_child(presence
, "show");
321 purple_xmlnode_insert_data(show
, show_string
, -1);
325 status
= purple_xmlnode_new_child(presence
, "status");
326 purple_xmlnode_insert_data(status
, msg
, -1);
330 char *pstr
= g_strdup_printf("%d", priority
);
331 pri
= purple_xmlnode_new_child(presence
, "priority");
332 purple_xmlnode_insert_data(pri
, pstr
, -1);
336 /* if we are idle and not offline, include idle */
337 if (js
->idle
&& state
!= JABBER_BUDDY_STATE_UNAVAILABLE
) {
338 PurpleXmlNode
*query
= purple_xmlnode_new_child(presence
, "query");
340 g_snprintf(seconds
, 10, "%d", (int) (time(NULL
) - js
->idle
));
342 purple_xmlnode_set_namespace(query
, NS_LAST_ACTIVITY
);
343 purple_xmlnode_set_attrib(query
, "seconds", seconds
);
348 jabber_caps_calculate_own_hash(js
);
350 c
= purple_xmlnode_new_child(presence
, "c");
351 purple_xmlnode_set_namespace(c
, "http://jabber.org/protocol/caps");
352 purple_xmlnode_set_attrib(c
, "node", CAPS0115_NODE
);
353 purple_xmlnode_set_attrib(c
, "hash", "sha-1");
354 purple_xmlnode_set_attrib(c
, "ver", jabber_caps_get_own_hash(js
));
358 * MASSIVE HUGE DISGUSTING HACK
359 * This is a huge hack. As far as I can tell, Google Talk's gmail client
360 * doesn't bother to check the actual features we advertise; they
361 * just assume that if we specify a 'voice-v1' ext (ignoring that
362 * these are to be assigned no semantic value), we support receiving voice
365 * Ditto for 'video-v1'.
367 audio_enabled
= jabber_audio_enabled(js
, NULL
/* unused */);
368 video_enabled
= jabber_video_enabled(js
, NULL
/* unused */);
370 if (audio_enabled
&& video_enabled
)
371 purple_xmlnode_set_attrib(c
, "ext", "voice-v1 camera-v1 video-v1");
372 else if (audio_enabled
)
373 purple_xmlnode_set_attrib(c
, "ext", "voice-v1");
374 else if (video_enabled
)
375 purple_xmlnode_set_attrib(c
, "ext", "camera-v1 video-v1");
381 struct _jabber_add_permit
{
382 PurpleConnection
*gc
;
387 static void authorize_add_cb(const char *message
, gpointer data
)
389 struct _jabber_add_permit
*jap
= data
;
391 PURPLE_ASSERT_CONNECTION_IS_VALID(jap
->gc
);
393 jabber_presence_subscription_set(purple_connection_get_protocol_data(jap
->gc
),
394 jap
->who
, "subscribed");
400 static void deny_add_cb(const char *message
, gpointer data
)
402 struct _jabber_add_permit
*jap
= data
;
404 PURPLE_ASSERT_CONNECTION_IS_VALID(jap
->gc
);
406 jabber_presence_subscription_set(purple_connection_get_protocol_data(jap
->gc
),
407 jap
->who
, "unsubscribed");
414 jabber_vcard_parse_avatar(JabberStream
*js
, const char *from
,
415 JabberIqType type
, const char *id
,
416 PurpleXmlNode
*packet
, gpointer blah
)
418 JabberBuddy
*jb
= NULL
;
419 PurpleXmlNode
*vcard
, *photo
, *binval
, *fn
, *nick
;
425 jb
= jabber_buddy_find(js
, from
, TRUE
);
427 js
->pending_avatar_requests
= g_slist_remove(js
->pending_avatar_requests
, jb
);
429 if((vcard
= purple_xmlnode_get_child(packet
, "vCard")) ||
430 (vcard
= purple_xmlnode_get_child_with_namespace(packet
, "query", "vcard-temp"))) {
431 /* The logic here regarding the nickname and full name is copied from
432 * buddy.c:jabber_vcard_parse. */
433 gchar
*nickname
= NULL
;
434 if ((fn
= purple_xmlnode_get_child(vcard
, "FN")))
435 nickname
= purple_xmlnode_get_data(fn
);
437 if ((nick
= purple_xmlnode_get_child(vcard
, "NICKNAME"))) {
438 char *tmp
= purple_xmlnode_get_data(nick
);
439 char *bare_jid
= jabber_get_bare_jid(from
);
440 if (tmp
&& strstr(bare_jid
, tmp
) == NULL
) {
450 purple_serv_got_alias(js
->gc
, from
, nickname
);
454 if ((photo
= purple_xmlnode_get_child(vcard
, "PHOTO"))) {
459 if ((binval
= purple_xmlnode_get_child(photo
, "BINVAL")) &&
460 (text
= purple_xmlnode_get_data(binval
))) {
461 data
= purple_base64_decode(text
, &size
);
465 hash
= jabber_calculate_data_hash(data
, size
, "sha1");
468 purple_buddy_icons_set_for_user(purple_connection_get_account(js
->gc
), from
, data
, size
, hash
);
475 typedef struct _JabberPresenceCapabilities
{
479 } JabberPresenceCapabilities
;
482 jabber_presence_set_capabilities(JabberCapsClientInfo
*info
, GList
*exts
,
483 JabberPresenceCapabilities
*userdata
)
485 JabberBuddyResource
*jbr
;
486 char *resource
= strchr(userdata
->from
, '/');
491 jbr
= jabber_buddy_find_resource(userdata
->jb
, resource
);
493 g_free(userdata
->from
);
496 g_list_foreach(exts
, (GFunc
)g_free
, NULL
);
502 /* Any old jbr->caps.info is owned by the caps code */
503 if (jbr
->caps
.exts
) {
504 g_list_foreach(jbr
->caps
.exts
, (GFunc
)g_free
, NULL
);
505 g_list_free(jbr
->caps
.exts
);
508 jbr
->caps
.info
= info
;
509 jbr
->caps
.exts
= exts
;
511 purple_protocol_got_media_caps(
512 purple_connection_get_account(userdata
->js
->gc
),
517 if (!jbr
->commands_fetched
&& jabber_resource_has_capability(jbr
, "http://jabber.org/protocol/commands")) {
518 JabberIq
*iq
= jabber_iq_new_query(userdata
->js
, JABBER_IQ_GET
, NS_DISCO_ITEMS
);
519 PurpleXmlNode
*query
= purple_xmlnode_get_child_with_namespace(iq
->node
, "query", NS_DISCO_ITEMS
);
520 purple_xmlnode_set_attrib(iq
->node
, "to", userdata
->from
);
521 purple_xmlnode_set_attrib(query
, "node", "http://jabber.org/protocol/commands");
522 jabber_iq_set_callback(iq
, jabber_adhoc_disco_result_cb
, NULL
);
525 jbr
->commands_fetched
= TRUE
;
530 * Versions of libpurple before 2.6.0 didn't advertise this capability, so
531 * we can't yet use Entity Capabilities to determine whether or not the
532 * other client supports Chat States.
534 if (jabber_resource_has_capability(jbr
, "http://jabber.org/protocol/chatstates"))
535 jbr
->chat_states
= JABBER_CHAT_STATES_SUPPORTED
;
537 jbr
->chat_states
= JABBER_CHAT_STATES_UNSUPPORTED
;
541 g_free(userdata
->from
);
546 handle_presence_chat(JabberStream
*js
, JabberPresence
*presence
, PurpleXmlNode
*packet
)
549 PurpleChatUserFlags flags
= PURPLE_CHAT_USER_NONE
;
550 JabberChat
*chat
= presence
->chat
;
552 if (presence
->state
== JABBER_BUDDY_STATE_ERROR
) {
553 char *title
, *msg
= jabber_parse_error(js
, packet
, NULL
);
556 title
= g_strdup_printf(_("Error joining chat %s"), presence
->from
);
557 purple_serv_got_join_chat_failed(js
->gc
, chat
->components
);
559 title
= g_strdup_printf(_("Error in chat %s"), presence
->from
);
560 if (g_hash_table_size(chat
->members
) == 0)
561 purple_serv_got_chat_left(js
->gc
, chat
->id
);
563 purple_notify_error(js
->gc
, title
, title
, msg
,
564 purple_request_cpar_from_connection(js
->gc
));
568 if (g_hash_table_size(chat
->members
) == 0)
569 /* Only destroy the chat if the error happened while joining */
570 jabber_chat_destroy(chat
);
574 if (presence
->type
== JABBER_PRESENCE_AVAILABLE
) {
575 const char *jid
= NULL
;
576 const char *affiliation
= NULL
;
577 const char *role
= NULL
;
578 gboolean is_our_resource
= FALSE
; /* Is the presence about us? */
579 JabberBuddyResource
*jbr
;
582 * XEP-0045 mandates the presence to include a resource (which is
583 * treated as the chat nick). Some non-compliant servers allow
584 * joining without a nick.
586 if (!presence
->jid_from
->resource
)
589 if (presence
->chat_info
.item
) {
590 jid
= purple_xmlnode_get_attrib(presence
->chat_info
.item
, "jid");
591 affiliation
= purple_xmlnode_get_attrib(presence
->chat_info
.item
, "affiliation");
592 role
= purple_xmlnode_get_attrib(presence
->chat_info
.item
, "role");
595 if (g_slist_find(presence
->chat_info
.codes
, GINT_TO_POINTER(110)) ||
596 g_str_equal(presence
->jid_from
->resource
, chat
->handle
) ||
597 purple_strequal(presence
->to
, jid
))
598 is_our_resource
= TRUE
;
600 if (g_slist_find(presence
->chat_info
.codes
, GINT_TO_POINTER(201))) {
601 chat
->config_dialog_type
= PURPLE_REQUEST_ACTION
;
602 chat
->config_dialog_handle
=
603 purple_request_action(js
->gc
,
604 _("Create New Room"),
605 _("Create New Room"),
606 _("You are creating a new room. Would"
607 " you like to configure it, or"
608 " accept the default settings?"),
609 /* Default Action */ 1,
610 purple_request_cpar_from_conversation(PURPLE_CONVERSATION(chat
->conv
)),
612 _("_Configure Room"), G_CALLBACK(jabber_chat_request_room_configure
),
613 _("_Accept Defaults"), G_CALLBACK(jabber_chat_create_instant_room
));
616 if (g_slist_find(presence
->chat_info
.codes
, GINT_TO_POINTER(210))) {
617 /* server rewrote room-nick */
618 g_free(chat
->handle
);
619 chat
->handle
= g_strdup(presence
->jid_from
->resource
);
622 if (purple_strequal(affiliation
, "owner"))
623 flags
|= PURPLE_CHAT_USER_FOUNDER
;
625 if (g_str_equal(role
, "moderator"))
626 flags
|= PURPLE_CHAT_USER_OP
;
627 else if (g_str_equal(role
, "participant"))
628 flags
|= PURPLE_CHAT_USER_VOICE
;
632 char *room_jid
= g_strdup_printf("%s@%s", presence
->jid_from
->node
, presence
->jid_from
->domain
);
634 chat
->conv
= purple_serv_got_joined_chat(js
->gc
, chat
->id
, room_jid
);
635 purple_chat_conversation_set_nick(chat
->conv
, chat
->handle
);
637 jabber_chat_disco_traffic(chat
);
641 jbr
= jabber_buddy_track_resource(presence
->jb
, presence
->jid_from
->resource
, presence
->priority
, presence
->state
, presence
->status
);
642 jbr
->commands_fetched
= TRUE
;
644 jabber_chat_track_handle(chat
, presence
->jid_from
->resource
, jid
, affiliation
, role
);
646 if(!jabber_chat_find_buddy(chat
->conv
, presence
->jid_from
->resource
))
647 purple_chat_conversation_add_user(chat
->conv
, presence
->jid_from
->resource
,
648 jid
, flags
, chat
->joined
> 0 && ((!presence
->delayed
) || (presence
->sent
> chat
->joined
)));
650 purple_chat_user_set_flags(purple_chat_conversation_find_user(chat
->conv
, presence
->jid_from
->resource
),
653 if (is_our_resource
&& chat
->joined
== 0)
654 chat
->joined
= time(NULL
);
656 } else if (presence
->type
== JABBER_PRESENCE_UNAVAILABLE
) {
657 gboolean nick_change
= FALSE
;
658 gboolean kick
= FALSE
;
659 gboolean is_our_resource
= FALSE
; /* Is the presence about us? */
661 const char *jid
= NULL
;
663 /* If the chat nick is invalid, we haven't yet joined, or we've
664 * already left (it was probably us leaving after we closed the
665 * chat), we don't care.
667 if (!presence
->jid_from
->resource
|| !chat
->conv
|| chat
->left
) {
669 presence
->jid_from
->resource
&& chat
->handle
&& !strcmp(presence
->jid_from
->resource
, chat
->handle
))
670 jabber_chat_destroy(chat
);
674 is_our_resource
= g_str_equal(presence
->jid_from
->resource
, chat
->handle
);
676 jabber_buddy_remove_resource(presence
->jb
, presence
->jid_from
->resource
);
678 if (presence
->chat_info
.item
)
679 jid
= purple_xmlnode_get_attrib(presence
->chat_info
.item
, "jid");
682 if (g_slist_find(presence
->chat_info
.codes
, GINT_TO_POINTER(110))) {
683 is_our_resource
= TRUE
;
687 if (g_slist_find(presence
->chat_info
.codes
, GINT_TO_POINTER(301))) {
688 /* XXX: We got banned. YAY! (No GIR, that's bad) */
692 if (g_slist_find(presence
->chat_info
.codes
, GINT_TO_POINTER(303))) {
693 const char *nick
= NULL
;
694 if (presence
->chat_info
.item
)
695 nick
= purple_xmlnode_get_attrib(presence
->chat_info
.item
, "nick");
699 purple_debug_warning("jabber", "Chat presence indicating a nick change, but no new nickname!\n");
703 if (g_str_equal(presence
->jid_from
->resource
, chat
->handle
)) {
704 /* Changing our own nickname */
705 g_free(chat
->handle
);
706 /* TODO: This should be resourceprep'd */
707 chat
->handle
= g_strdup(nick
);
710 purple_chat_conversation_rename_user(chat
->conv
,
711 presence
->jid_from
->resource
,
713 jabber_chat_remove_handle(chat
,
714 presence
->jid_from
->resource
);
718 if (g_slist_find(presence
->chat_info
.codes
, GINT_TO_POINTER(307))) {
719 /* Someone was kicked from the room */
720 const char *actor
= NULL
;
726 if (presence
->chat_info
.item
) {
729 node
= purple_xmlnode_get_child(presence
->chat_info
.item
, "actor");
731 actor
= purple_xmlnode_get_attrib(node
, "jid");
732 node
= purple_xmlnode_get_child(presence
->chat_info
.item
, "reason");
734 reason
= purple_xmlnode_get_data(node
);
738 reason
= g_strdup(_("No reason"));
740 if (is_our_resource
) {
742 tmp
= g_strdup_printf(_("You have been kicked by %s: (%s)"),
745 tmp
= g_strdup_printf(_("You have been kicked: (%s)"),
749 tmp
= g_strdup_printf(_("Kicked by %s (%s)"),
752 tmp
= g_strdup_printf(_("Kicked (%s)"),
756 g_free(presence
->status
);
757 presence
->status
= tmp
;
762 if (g_slist_find(presence
->chat_info
.codes
, GINT_TO_POINTER(321))) {
763 /* XXX: removed due to an affiliation change */
766 if (g_slist_find(presence
->chat_info
.codes
, GINT_TO_POINTER(322))) {
767 /* XXX: removed because room is now members-only */
770 if (g_slist_find(presence
->chat_info
.codes
, GINT_TO_POINTER(332))) {
771 /* XXX: removed due to system shutdown */
776 * Possibly another connected resource of our JID (see XEP-0045
777 * v1.24 section 7.1.10) being disconnected. Should be
778 * distinguished by the item_jid.
779 * Also possibly works around bits of an Openfire bug. See
782 if (is_our_resource
&& jid
&& !purple_strequal(presence
->to
, jid
)) {
783 /* TODO: When the above is a loop, this needs to still act
784 * sanely for all cases (this code is a little fragile). */
785 if (!kick
&& !nick_change
)
786 /* Presumably, kicks and nick changes also affect us. */
787 is_our_resource
= FALSE
;
791 if (is_our_resource
) {
793 gchar
*msg
= g_strdup_printf("%s: %s",
794 presence
->jid_from
->resource
,
796 purple_conversation_write_system_message(PURPLE_CONVERSATION(chat
->conv
), msg
, 0);
799 purple_serv_got_chat_left(js
->gc
, chat
->id
);
800 jabber_chat_destroy(chat
);
802 purple_chat_conversation_remove_user(chat
->conv
, presence
->jid_from
->resource
,
804 jabber_chat_remove_handle(chat
, presence
->jid_from
->resource
);
813 handle_presence_contact(JabberStream
*js
, JabberPresence
*presence
)
815 JabberBuddyResource
*jbr
;
816 PurpleAccount
*account
;
819 PurpleIMConversation
*im
;
821 buddy_name
= jabber_id_get_bare_jid(presence
->jid_from
);
823 account
= purple_connection_get_account(js
->gc
);
824 b
= purple_blist_find_buddy(account
, buddy_name
);
827 * Unbind/unlock from sending messages to a specific resource on
828 * presence changes. This is locked to a specific resource when
829 * receiving a message (in message.c).
831 im
= purple_conversations_find_im_with_account(buddy_name
, account
);
833 purple_debug_info("jabber", "Changed conversation binding from %s to %s\n",
834 purple_conversation_get_name(PURPLE_CONVERSATION(im
)), buddy_name
);
835 purple_conversation_set_name(PURPLE_CONVERSATION(im
), buddy_name
);
839 if (presence
->jb
!= js
->user_jb
) {
840 purple_debug_warning("jabber", "Got presence for unknown buddy %s on account %s (%p)\n",
841 buddy_name
, purple_account_get_username(account
), account
);
845 /* this is a different resource of our own account. Resume even when this account isn't on our blist */
849 if (b
&& presence
->vcard_avatar_hash
) {
850 const char *ah
= presence
->vcard_avatar_hash
[0] != '\0' ?
851 presence
->vcard_avatar_hash
: NULL
;
852 const char *ah2
= purple_buddy_icons_get_checksum_for_user(b
);
853 if (!purple_strequal(ah
, ah2
)) {
854 /* XXX this is a crappy way of trying to prevent
855 * someone from spamming us with presence packets
856 * and causing us to DoS ourselves...what we really
857 * need is a queue system that can throttle itself,
858 * but i'm too tired to write that right now */
859 if(!g_slist_find(js
->pending_avatar_requests
, presence
->jb
)) {
861 PurpleXmlNode
*vcard
;
863 js
->pending_avatar_requests
=
864 g_slist_prepend(js
->pending_avatar_requests
, presence
->jb
);
866 iq
= jabber_iq_new(js
, JABBER_IQ_GET
);
867 purple_xmlnode_set_attrib(iq
->node
, "to", buddy_name
);
868 vcard
= purple_xmlnode_new_child(iq
->node
, "vCard");
869 purple_xmlnode_set_namespace(vcard
, "vcard-temp");
871 jabber_iq_set_callback(iq
, jabber_vcard_parse_avatar
, NULL
);
877 if (presence
->state
== JABBER_BUDDY_STATE_ERROR
||
878 presence
->type
== JABBER_PRESENCE_UNAVAILABLE
||
879 presence
->type
== JABBER_PRESENCE_UNSUBSCRIBED
) {
880 jabber_buddy_remove_resource(presence
->jb
, presence
->jid_from
->resource
);
882 jbr
= jabber_buddy_track_resource(presence
->jb
,
883 presence
->jid_from
->resource
, presence
->priority
,
884 presence
->state
, presence
->status
);
885 jbr
->idle
= presence
->idle
? time(NULL
) - presence
->idle
: 0;
888 jbr
= jabber_buddy_find_resource(presence
->jb
, NULL
);
890 jabber_google_presence_incoming(js
, buddy_name
, jbr
);
891 purple_protocol_got_user_status(account
, buddy_name
,
892 jabber_buddy_state_get_status_id(jbr
->state
),
893 "priority", jbr
->priority
,
894 "message", jbr
->status
,
896 purple_protocol_got_user_idle(account
, buddy_name
,
897 jbr
->idle
, jbr
->idle
);
898 if (presence
->nickname
)
899 purple_serv_got_alias(js
->gc
, buddy_name
, presence
->nickname
);
901 purple_protocol_got_user_status(account
, buddy_name
,
902 jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_UNAVAILABLE
),
903 presence
->status
? "message" : NULL
, presence
->status
,
911 void jabber_presence_parse(JabberStream
*js
, PurpleXmlNode
*packet
)
914 JabberBuddyResource
*jbr
= NULL
;
915 gboolean signal_return
, ret
;
916 JabberPresence presence
;
917 PurpleXmlNode
*child
;
919 memset(&presence
, 0, sizeof(presence
));
921 presence
.state
= JABBER_BUDDY_STATE_UNKNOWN
;
922 presence
.sent
= time(NULL
);
923 /* interesting values */
924 presence
.from
= purple_xmlnode_get_attrib(packet
, "from");
925 presence
.to
= purple_xmlnode_get_attrib(packet
, "to");
926 type
= purple_xmlnode_get_attrib(packet
, "type");
927 presence
.type
= str_to_presence_type(type
);
929 presence
.jb
= jabber_buddy_find(js
, presence
.from
, TRUE
);
930 g_return_if_fail(presence
.jb
!= NULL
);
932 presence
.jid_from
= jabber_id_new(presence
.from
);
933 if (presence
.jid_from
== NULL
) {
934 purple_debug_error("jabber", "Ignoring presence with malformed 'from' "
935 "JID: %s\n", presence
.from
);
939 signal_return
= GPOINTER_TO_INT(purple_signal_emit_return_1(purple_connection_get_protocol(js
->gc
),
940 "jabber-receiving-presence", js
->gc
, type
, presence
.from
, packet
));
945 if (presence
.jid_from
->node
)
946 presence
.chat
= jabber_chat_find(js
, presence
.jid_from
->node
,
947 presence
.jid_from
->domain
);
948 g_free(presence
.jb
->error_msg
);
949 presence
.jb
->error_msg
= NULL
;
951 if (presence
.type
== JABBER_PRESENCE_AVAILABLE
) {
952 presence
.state
= JABBER_BUDDY_STATE_ONLINE
;
953 } else if (presence
.type
== JABBER_PRESENCE_ERROR
) {
954 /* TODO: Is this handled properly? Should it be treated as per-jbr? */
955 char *msg
= jabber_parse_error(js
, packet
, NULL
);
956 presence
.state
= JABBER_BUDDY_STATE_ERROR
;
957 presence
.jb
->error_msg
= msg
? msg
: g_strdup(_("Unknown Error in presence"));
958 } else if (presence
.type
== JABBER_PRESENCE_SUBSCRIBE
) {
959 /* TODO: Move to handle_subscribe() (so nick is extracted by the
961 struct _jabber_add_permit
*jap
= g_new0(struct _jabber_add_permit
, 1);
962 gboolean onlist
= FALSE
;
963 PurpleAccount
*account
;
967 account
= purple_connection_get_account(js
->gc
);
968 buddy
= purple_blist_find_buddy(account
, presence
.from
);
969 nick
= purple_xmlnode_get_child_with_namespace(packet
, "nick", "http://jabber.org/protocol/nick");
971 presence
.nickname
= purple_xmlnode_get_data(nick
);
974 if ((presence
.jb
->subscription
& (JABBER_SUB_TO
| JABBER_SUB_PENDING
)))
979 jap
->who
= g_strdup(presence
.from
);
982 purple_account_request_authorization(account
, presence
.from
, NULL
, presence
.nickname
,
983 NULL
, onlist
, authorize_add_cb
, deny_add_cb
, jap
);
986 } else if (presence
.type
== JABBER_PRESENCE_SUBSCRIBED
) {
987 /* This case (someone has approved our subscribe request) is handled
988 * by the roster push the server sends along with this.
991 } else if (presence
.type
== JABBER_PRESENCE_UNSUBSCRIBE
) {
992 /* XXX I'm not sure this is the right way to handle this, it
993 * might be better to add "unsubscribe" to the presence status
994 * if lower down, but I'm not sure. */
995 /* they are unsubscribing from our presence, we don't care */
996 /* Well, maybe just a little, we might want/need to start
997 * acknowledging this (and the others) at some point. */
999 } else if (presence
.type
== JABBER_PRESENCE_PROBE
) {
1000 purple_debug_warning("jabber", "Ignoring presence probe\n");
1002 } else if (presence
.type
== JABBER_PRESENCE_UNAVAILABLE
) {
1003 presence
.state
= JABBER_BUDDY_STATE_UNAVAILABLE
;
1004 } else if (presence
.type
== JABBER_PRESENCE_UNSUBSCRIBED
) {
1005 presence
.state
= JABBER_BUDDY_STATE_UNKNOWN
;
1007 purple_debug_warning("jabber", "Ignoring presence with invalid type "
1012 for (child
= packet
->child
; child
; child
= child
->next
) {
1015 JabberPresenceHandler
*pih
;
1016 if (child
->type
!= PURPLE_XMLNODE_TYPE_TAG
)
1019 xmlns
= purple_xmlnode_get_namespace(child
);
1020 key
= g_strdup_printf("%s %s", child
->name
, xmlns
? xmlns
: "");
1021 pih
= g_hash_table_lookup(presence_handlers
, key
);
1024 pih(js
, &presence
, child
);
1027 if (presence
.delayed
&& presence
.idle
&& presence
.adjust_idle_for_delay
) {
1028 /* Delayed and idle, so update idle time */
1029 presence
.idle
= presence
.idle
+ (time(NULL
) - presence
.sent
);
1032 /* TODO: Handle tracking jb(r) here? */
1035 ret
= handle_presence_chat(js
, &presence
, packet
);
1037 ret
= handle_presence_contact(js
, &presence
);
1041 if (presence
.caps
&& presence
.type
== JABBER_PRESENCE_AVAILABLE
) {
1042 /* handle Entity Capabilities (XEP-0115) */
1043 const char *node
= purple_xmlnode_get_attrib(presence
.caps
, "node");
1044 const char *ver
= purple_xmlnode_get_attrib(presence
.caps
, "ver");
1045 const char *hash
= purple_xmlnode_get_attrib(presence
.caps
, "hash");
1046 const char *ext
= purple_xmlnode_get_attrib(presence
.caps
, "ext");
1048 /* v1.3 uses: node, ver, and optionally ext.
1049 * v1.5 uses: node, ver, and hash. */
1050 if (node
&& *node
&& ver
&& *ver
) {
1051 gchar
**exts
= ext
&& *ext
? g_strsplit(ext
, " ", -1) : NULL
;
1052 jbr
= jabber_buddy_find_resource(presence
.jb
, presence
.jid_from
->resource
);
1054 /* Look it up if we don't already have all this information */
1055 if (!jbr
|| !jbr
->caps
.info
||
1056 !g_str_equal(node
, jbr
->caps
.info
->tuple
.node
) ||
1057 !g_str_equal(ver
, jbr
->caps
.info
->tuple
.ver
) ||
1058 !purple_strequal(hash
, jbr
->caps
.info
->tuple
.hash
) ||
1059 !jabber_caps_exts_known(jbr
->caps
.info
, (gchar
**)exts
)) {
1060 JabberPresenceCapabilities
*userdata
= g_new0(JabberPresenceCapabilities
, 1);
1062 userdata
->jb
= presence
.jb
;
1063 userdata
->from
= g_strdup(presence
.from
);
1064 jabber_caps_get_info(js
, presence
.from
, node
, ver
, hash
, exts
,
1065 (jabber_caps_get_info_cb
)jabber_presence_set_capabilities
,
1075 while (presence
.chat_info
.codes
)
1076 presence
.chat_info
.codes
=
1077 g_slist_delete_link(presence
.chat_info
.codes
,
1078 presence
.chat_info
.codes
);
1080 g_free(presence
.status
);
1081 g_free(presence
.vcard_avatar_hash
);
1082 g_free(presence
.nickname
);
1083 jabber_id_free(presence
.jid_from
);
1086 void jabber_presence_subscription_set(JabberStream
*js
, const char *who
, const char *type
)
1088 PurpleXmlNode
*presence
= purple_xmlnode_new("presence");
1090 purple_xmlnode_set_attrib(presence
, "to", who
);
1091 purple_xmlnode_set_attrib(presence
, "type", type
);
1093 jabber_send(js
, presence
);
1094 purple_xmlnode_free(presence
);
1097 void purple_status_to_jabber(const PurpleStatus
*status
, JabberBuddyState
*state
, char **msg
, int *priority
)
1099 const char *status_id
= NULL
;
1100 const char *formatted_msg
= NULL
;
1102 if(state
) *state
= JABBER_BUDDY_STATE_UNKNOWN
;
1103 if(msg
) *msg
= NULL
;
1104 if(priority
) *priority
= 0;
1107 if(state
) *state
= JABBER_BUDDY_STATE_UNAVAILABLE
;
1110 status_id
= purple_status_get_id(status
);
1111 *state
= jabber_buddy_status_id_get_state(status_id
);
1115 formatted_msg
= purple_status_get_attr_string(status
, "message");
1117 /* if the message is blank, then there really isn't a message */
1118 if(formatted_msg
&& *formatted_msg
)
1119 *msg
= purple_markup_strip_html(formatted_msg
);
1123 *priority
= purple_status_get_attr_int(status
, "priority");
1127 /* Incoming presence handlers */
1129 parse_priority(JabberStream
*js
, JabberPresence
*presence
, PurpleXmlNode
*priority
)
1131 char *p
= purple_xmlnode_get_data(priority
);
1133 if (presence
->priority
!= 0)
1134 purple_debug_warning("jabber", "presence stanza received with multiple "
1135 "priority children!?\n");
1138 presence
->priority
= atoi(p
);
1141 purple_debug_warning("jabber", "Empty <priority/> in presence!\n");
1145 parse_show(JabberStream
*js
, JabberPresence
*presence
, PurpleXmlNode
*show
)
1149 if (presence
->type
!= JABBER_PRESENCE_AVAILABLE
) {
1150 purple_debug_warning("jabber", "<show/> present on presence, but "
1151 "type is not default ('available')\n");
1155 cdata
= purple_xmlnode_get_data(show
);
1157 presence
->state
= jabber_buddy_show_get_state(cdata
);
1160 purple_debug_warning("jabber", "<show/> present on presence, but "
1165 parse_status(JabberStream
*js
, JabberPresence
*presence
, PurpleXmlNode
*status
)
1167 /* TODO: Check/track language attribute? */
1169 g_free(presence
->status
);
1170 presence
->status
= purple_xmlnode_get_data(status
);
1174 parse_delay(JabberStream
*js
, JabberPresence
*presence
, PurpleXmlNode
*delay
)
1176 const char *stamp
= purple_xmlnode_get_attrib(delay
, "stamp");
1177 presence
->delayed
= TRUE
;
1178 presence
->sent
= purple_str_to_time(stamp
, TRUE
, NULL
, NULL
, NULL
);
1182 parse_apple_idle(JabberStream
*js
, JabberPresence
*presence
, PurpleXmlNode
*x
)
1184 PurpleXmlNode
*since
= purple_xmlnode_get_child(x
, "idle-since");
1186 char *stamp
= purple_xmlnode_get_data_unescaped(since
);
1188 time_t tstamp
= purple_str_to_time(stamp
, TRUE
, NULL
, NULL
, NULL
);
1190 presence
->idle
= time(NULL
) - tstamp
;
1191 presence
->adjust_idle_for_delay
= FALSE
;
1192 if(presence
->idle
< 0) {
1193 purple_debug_warning("jabber", "Received bogus idle timestamp %s\n", stamp
);
1203 parse_idle(JabberStream
*js
, JabberPresence
*presence
, PurpleXmlNode
*query
)
1205 const gchar
*seconds
= purple_xmlnode_get_attrib(query
, "seconds");
1207 presence
->idle
= atoi(seconds
);
1208 presence
->adjust_idle_for_delay
= TRUE
;
1209 if (presence
->idle
< 0) {
1210 purple_debug_warning("jabber", "Received bogus idle time %s\n", seconds
);
1217 parse_caps(JabberStream
*js
, JabberPresence
*presence
, PurpleXmlNode
*c
)
1219 /* TODO: Move the rest of the caps handling in here, after changing the
1220 * the "do we have details about this (node, ver) and exts" to not
1221 * require the jbr to be present (since that happens later).
1227 parse_nickname(JabberStream
*js
, JabberPresence
*presence
, PurpleXmlNode
*nick
)
1229 g_free(presence
->nickname
);
1230 presence
->nickname
= purple_xmlnode_get_data(nick
);
1234 parse_vcard_avatar(JabberStream
*js
, JabberPresence
*presence
, PurpleXmlNode
*x
)
1236 PurpleXmlNode
*photo
= purple_xmlnode_get_child(x
, "photo");
1239 char *hash_tmp
= purple_xmlnode_get_data(photo
);
1240 g_free(presence
->vcard_avatar_hash
);
1241 presence
->vcard_avatar_hash
=
1242 hash_tmp
? hash_tmp
: g_strdup("");
1247 parse_muc_user(JabberStream
*js
, JabberPresence
*presence
, PurpleXmlNode
*x
)
1249 PurpleXmlNode
*status
;
1251 if (presence
->chat
== NULL
) {
1252 purple_debug_warning("jabber", "Ignoring MUC gloop on non-MUC presence\n");
1256 if (presence
->chat
->conv
== NULL
)
1257 presence
->chat
->muc
= TRUE
;
1259 for (status
= purple_xmlnode_get_child(x
, "status"); status
;
1260 status
= purple_xmlnode_get_next_twin(status
)) {
1261 const char *code
= purple_xmlnode_get_attrib(status
, "code");
1267 if (val
== 0 || val
< 0) {
1268 purple_debug_warning("jabber", "Ignoring bogus status code '%s'\n",
1273 presence
->chat_info
.codes
= g_slist_prepend(presence
->chat_info
.codes
, GINT_TO_POINTER(val
));
1276 presence
->chat_info
.item
= purple_xmlnode_get_child(x
, "item");
1279 void jabber_presence_register_handler(const char *node
, const char *xmlns
,
1280 JabberPresenceHandler
*handler
)
1283 * This is valid because nodes nor namespaces cannot have spaces in them
1284 * (see http://www.w3.org/TR/2006/REC-xml-20060816/ and
1285 * http://www.w3.org/TR/REC-xml-names/)
1287 char *key
= g_strdup_printf("%s %s", node
, xmlns
);
1288 g_hash_table_replace(presence_handlers
, key
, handler
);
1291 void jabber_presence_init(void)
1293 presence_handlers
= g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, NULL
);
1295 /* Core RFC things */
1296 jabber_presence_register_handler("priority", "jabber:client", parse_priority
);
1297 jabber_presence_register_handler("show", "jabber:client", parse_show
);
1298 jabber_presence_register_handler("status", "jabber:client", parse_status
);
1301 jabber_presence_register_handler("c", "http://jabber.org/protocol/caps", parse_caps
);
1302 jabber_presence_register_handler("delay", NS_DELAYED_DELIVERY
, parse_delay
);
1303 jabber_presence_register_handler("nick", "http://jabber.org/protocol/nick", parse_nickname
);
1304 jabber_presence_register_handler("query", NS_LAST_ACTIVITY
, parse_idle
);
1305 jabber_presence_register_handler("x", NS_DELAYED_DELIVERY_LEGACY
, parse_delay
);
1306 jabber_presence_register_handler("x", "http://jabber.org/protocol/muc#user", parse_muc_user
);
1307 jabber_presence_register_handler("x", "vcard-temp:x:update", parse_vcard_avatar
);
1310 jabber_presence_register_handler("x", NS_APPLE_IDLE
, parse_apple_idle
);
1313 void jabber_presence_uninit(void)
1315 g_hash_table_destroy(presence_handlers
);
1316 presence_handlers
= NULL
;