Migrate certificates, icons, logs to XDG dirs
[pidgin-git.git] / libpurple / protocols / jabber / presence.c
blob3bd17bfcf482e3748a1d6580f246d7a626d0fc9e
1 /*
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
6 * source distribution.
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
23 #include "internal.h"
25 #include "account.h"
26 #include "conversation.h"
27 #include "debug.h"
28 #include "notify.h"
29 #include "request.h"
30 #include "server.h"
31 #include "status.h"
32 #include "util.h"
33 #include "xmlnode.h"
35 #include "buddy.h"
36 #include "chat.h"
37 #include "google/google.h"
38 #include "google/google_presence.h"
39 #include "presence.h"
40 #include "iq.h"
41 #include "jutil.h"
42 #include "adhoccommands.h"
44 #include "usermood.h"
45 #include "usertune.h"
47 static GHashTable *presence_handlers = NULL;
49 static const struct {
50 const char *name;
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)
66 gsize i;
68 if (type == NULL)
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,
80 gpointer user_data)
82 JabberChat *chat = val;
83 PurpleXmlNode *presence = user_data;
84 char *chat_full_jid;
86 if(!chat->conv || chat->left)
87 return;
89 chat_full_jid = g_strdup_printf("%s@%s/%s", chat->room, chat->server,
90 chat->handle);
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;
101 JabberBuddy *jb;
102 JabberBuddyResource *jbr;
103 const char *username;
104 JabberBuddyState state;
105 char *msg;
106 int priority;
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);
113 if (status == NULL)
114 status = purple_presence_get_active_status(presence);
115 purple_status_to_jabber(status, &state, &msg, &priority);
117 jb = js->user_jb;
119 if (state == JABBER_BUDDY_STATE_UNAVAILABLE ||
120 state == JABBER_BUDDY_STATE_UNKNOWN) {
121 jabber_buddy_remove_resource(jb, js->user->resource);
122 } else {
123 jbr = jabber_buddy_track_resource(jb, js->user->resource, priority,
124 state, msg);
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);
135 if (jbr) {
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,
140 NULL);
141 purple_protocol_got_user_idle(account, username, jbr->idle, jbr->idle);
142 } else {
143 purple_protocol_got_user_status(account, username, "offline",
144 msg ? "message" : NULL, msg,
145 NULL);
148 g_free(msg);
151 void jabber_set_status(PurpleAccount *account, PurpleStatus *status)
153 PurpleConnection *gc;
154 JabberStream *js;
156 if (!purple_account_is_connected(account))
157 return;
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. */
162 return;
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) {
170 const char *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);
175 return;
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;
187 int priority;
188 const char *artist = NULL, *title = NULL, *source = NULL, *uri = NULL, *track = NULL;
189 int length = -1;
190 gboolean allowBuzz;
191 PurplePresence *p;
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");
201 return;
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");
237 if (js->avatar_hash)
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 */
248 g_free(js->old_msg);
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;
256 g_free(stripped);
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 = {
272 (char*)artist,
273 (char*)title,
274 (char*)source,
275 (char*)track,
276 length,
277 (char*)uri
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);
285 g_free(js->old_uri);
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);
295 #undef CHANGED
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;
304 #ifdef USE_VV
305 gboolean audio_enabled, video_enabled;
306 #endif
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);
319 if(show_string) {
320 show = purple_xmlnode_new_child(presence, "show");
321 purple_xmlnode_insert_data(show, show_string, -1);
324 if(msg) {
325 status = purple_xmlnode_new_child(presence, "status");
326 purple_xmlnode_insert_data(status, msg, -1);
329 if(priority) {
330 char *pstr = g_strdup_printf("%d", priority);
331 pri = purple_xmlnode_new_child(presence, "priority");
332 purple_xmlnode_insert_data(pri, pstr, -1);
333 g_free(pstr);
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");
339 gchar seconds[10];
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);
346 /* JEP-0115 */
347 /* calculate hash */
348 jabber_caps_calculate_own_hash(js);
349 /* create xml */
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));
356 #ifdef USE_VV
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
363 * calls.
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");
376 #endif
378 return presence;
381 struct _jabber_add_permit {
382 PurpleConnection *gc;
383 JabberStream *js;
384 char *who;
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");
396 g_free(jap->who);
397 g_free(jap);
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");
409 g_free(jap->who);
410 g_free(jap);
413 static void
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;
420 char *text;
422 if(!from)
423 return;
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) {
441 g_free(nickname);
442 nickname = tmp;
443 } else
444 g_free(tmp);
446 g_free(bare_jid);
449 if (nickname) {
450 purple_serv_got_alias(js->gc, from, nickname);
451 g_free(nickname);
454 if ((photo = purple_xmlnode_get_child(vcard, "PHOTO"))) {
455 guchar *data = NULL;
456 gchar *hash = NULL;
457 gsize size = 0;
459 if ((binval = purple_xmlnode_get_child(photo, "BINVAL")) &&
460 (text = purple_xmlnode_get_data(binval))) {
461 data = purple_base64_decode(text, &size);
462 g_free(text);
464 if (data)
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);
470 g_free(hash);
475 typedef struct _JabberPresenceCapabilities {
476 JabberStream *js;
477 JabberBuddy *jb;
478 char *from;
479 } JabberPresenceCapabilities;
481 static void
482 jabber_presence_set_capabilities(JabberCapsClientInfo *info, GList *exts,
483 JabberPresenceCapabilities *userdata)
485 JabberBuddyResource *jbr;
486 char *resource = strchr(userdata->from, '/');
488 if (resource)
489 resource += 1;
491 jbr = jabber_buddy_find_resource(userdata->jb, resource);
492 if (!jbr) {
493 g_free(userdata->from);
494 g_free(userdata);
495 if (exts) {
496 g_list_foreach(exts, (GFunc)g_free, NULL);
497 g_list_free(exts);
499 return;
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),
513 userdata->from);
514 if (info == NULL)
515 goto out;
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);
523 jabber_iq_send(iq);
525 jbr->commands_fetched = TRUE;
528 #if 0
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;
536 else
537 jbr->chat_states = JABBER_CHAT_STATES_UNSUPPORTED;
538 #endif
540 out:
541 g_free(userdata->from);
542 g_free(userdata);
545 static gboolean
546 handle_presence_chat(JabberStream *js, JabberPresence *presence, PurpleXmlNode *packet)
548 static int i = 1;
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);
555 if (!chat->conv) {
556 title = g_strdup_printf(_("Error joining chat %s"), presence->from);
557 purple_serv_got_join_chat_failed(js->gc, chat->components);
558 } else {
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));
565 g_free(title);
566 g_free(msg);
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);
571 return FALSE;
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)
587 return FALSE;
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)),
611 chat, 2,
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;
624 if (role) {
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;
631 if(!chat->conv) {
632 char *room_jid = g_strdup_printf("%s@%s", presence->jid_from->node, presence->jid_from->domain);
633 chat->id = i++;
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);
638 g_free(room_jid);
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)));
649 else
650 purple_chat_user_set_flags(purple_chat_conversation_find_user(chat->conv, presence->jid_from->resource),
651 flags);
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) {
668 if (chat->left &&
669 presence->jid_from->resource && chat->handle && !strcmp(presence->jid_from->resource, chat->handle))
670 jabber_chat_destroy(chat);
671 return FALSE;
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");
681 if (chat->muc) {
682 if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(110))) {
683 is_our_resource = TRUE;
684 chat->joined = 0;
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");
697 /* nick change */
698 if (!nick) {
699 purple_debug_warning("jabber", "Chat presence indicating a nick change, but no new nickname!\n");
700 } else {
701 nick_change = TRUE;
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,
712 nick);
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;
721 char *reason = NULL;
722 char *tmp;
724 kick = TRUE;
726 if (presence->chat_info.item) {
727 PurpleXmlNode *node;
729 node = purple_xmlnode_get_child(presence->chat_info.item, "actor");
730 if (node)
731 actor = purple_xmlnode_get_attrib(node, "jid");
732 node = purple_xmlnode_get_child(presence->chat_info.item, "reason");
733 if (node)
734 reason = purple_xmlnode_get_data(node);
737 if (reason == NULL)
738 reason = g_strdup(_("No reason"));
740 if (is_our_resource) {
741 if (actor)
742 tmp = g_strdup_printf(_("You have been kicked by %s: (%s)"),
743 actor, reason);
744 else
745 tmp = g_strdup_printf(_("You have been kicked: (%s)"),
746 reason);
747 } else {
748 if (actor)
749 tmp = g_strdup_printf(_("Kicked by %s (%s)"),
750 actor, reason);
751 else
752 tmp = g_strdup_printf(_("Kicked (%s)"),
753 reason);
756 g_free(presence->status);
757 presence->status = tmp;
759 g_free(reason);
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
780 * #8319.
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;
790 if(!nick_change) {
791 if (is_our_resource) {
792 if (kick) {
793 gchar *msg = g_strdup_printf("%s: %s",
794 presence->jid_from->resource,
795 presence->status);
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);
801 } else {
802 purple_chat_conversation_remove_user(chat->conv, presence->jid_from->resource,
803 presence->status);
804 jabber_chat_remove_handle(chat, presence->jid_from->resource);
809 return TRUE;
812 static gboolean
813 handle_presence_contact(JabberStream *js, JabberPresence *presence)
815 JabberBuddyResource *jbr;
816 PurpleAccount *account;
817 PurpleBuddy *b;
818 char *buddy_name;
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);
832 if (im) {
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);
838 if (b == NULL) {
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);
842 g_free(buddy_name);
843 return FALSE;
844 } else {
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)) {
860 JabberIq *iq;
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);
872 jabber_iq_send(iq);
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);
881 } else {
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);
889 if (jbr) {
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,
895 NULL);
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);
900 } else {
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,
904 NULL);
906 g_free(buddy_name);
908 return TRUE;
911 void jabber_presence_parse(JabberStream *js, PurpleXmlNode *packet)
913 const char *type;
914 JabberBuddyResource *jbr = NULL;
915 gboolean signal_return, ret;
916 JabberPresence presence;
917 PurpleXmlNode *child;
919 memset(&presence, 0, sizeof(presence));
920 /* defaults */
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);
936 return;
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));
941 if (signal_return) {
942 goto out;
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
960 * PresenceHandler */
961 struct _jabber_add_permit *jap = g_new0(struct _jabber_add_permit, 1);
962 gboolean onlist = FALSE;
963 PurpleAccount *account;
964 PurpleBuddy *buddy;
965 PurpleXmlNode *nick;
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");
970 if (nick)
971 presence.nickname = purple_xmlnode_get_data(nick);
973 if (buddy) {
974 if ((presence.jb->subscription & (JABBER_SUB_TO | JABBER_SUB_PENDING)))
975 onlist = TRUE;
978 jap->gc = js->gc;
979 jap->who = g_strdup(presence.from);
980 jap->js = js;
982 purple_account_request_authorization(account, presence.from, NULL, presence.nickname,
983 NULL, onlist, authorize_add_cb, deny_add_cb, jap);
985 goto out;
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.
990 goto out;
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. */
998 goto out;
999 } else if (presence.type == JABBER_PRESENCE_PROBE) {
1000 purple_debug_warning("jabber", "Ignoring presence probe\n");
1001 goto out;
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;
1006 } else {
1007 purple_debug_warning("jabber", "Ignoring presence with invalid type "
1008 "'%s'\n", type);
1009 goto out;
1012 for (child = packet->child; child; child = child->next) {
1013 const char *xmlns;
1014 char *key;
1015 JabberPresenceHandler *pih;
1016 if (child->type != PURPLE_XMLNODE_TYPE_TAG)
1017 continue;
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);
1022 g_free(key);
1023 if (pih)
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? */
1034 if (presence.chat)
1035 ret = handle_presence_chat(js, &presence, packet);
1036 else
1037 ret = handle_presence_contact(js, &presence);
1038 if (!ret)
1039 goto out;
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);
1061 userdata->js = js;
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,
1066 userdata);
1067 } else {
1068 if (exts)
1069 g_strfreev(exts);
1074 out:
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;
1106 if(!status) {
1107 if(state) *state = JABBER_BUDDY_STATE_UNAVAILABLE;
1108 } else {
1109 if(state) {
1110 status_id = purple_status_get_id(status);
1111 *state = jabber_buddy_status_id_get_state(status_id);
1114 if(msg) {
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);
1122 if(priority)
1123 *priority = purple_status_get_attr_int(status, "priority");
1127 /* Incoming presence handlers */
1128 static void
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");
1137 if (p) {
1138 presence->priority = atoi(p);
1139 g_free(p);
1140 } else
1141 purple_debug_warning("jabber", "Empty <priority/> in presence!\n");
1144 static void
1145 parse_show(JabberStream *js, JabberPresence *presence, PurpleXmlNode *show)
1147 char *cdata;
1149 if (presence->type != JABBER_PRESENCE_AVAILABLE) {
1150 purple_debug_warning("jabber", "<show/> present on presence, but "
1151 "type is not default ('available')\n");
1152 return;
1155 cdata = purple_xmlnode_get_data(show);
1156 if (cdata) {
1157 presence->state = jabber_buddy_show_get_state(cdata);
1158 g_free(cdata);
1159 } else
1160 purple_debug_warning("jabber", "<show/> present on presence, but "
1161 "no contents!\n");
1164 static void
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);
1173 static void
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);
1181 static void
1182 parse_apple_idle(JabberStream *js, JabberPresence *presence, PurpleXmlNode *x)
1184 PurpleXmlNode *since = purple_xmlnode_get_child(x, "idle-since");
1185 if (since) {
1186 char *stamp = purple_xmlnode_get_data_unescaped(since);
1187 if (stamp) {
1188 time_t tstamp = purple_str_to_time(stamp, TRUE, NULL, NULL, NULL);
1189 if (tstamp != 0) {
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);
1194 presence->idle = 0;
1198 g_free(stamp);
1202 static void
1203 parse_idle(JabberStream *js, JabberPresence *presence, PurpleXmlNode *query)
1205 const gchar *seconds = purple_xmlnode_get_attrib(query, "seconds");
1206 if (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);
1211 presence->idle = 0;
1216 static void
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).
1223 presence->caps = c;
1226 static void
1227 parse_nickname(JabberStream *js, JabberPresence *presence, PurpleXmlNode *nick)
1229 g_free(presence->nickname);
1230 presence->nickname = purple_xmlnode_get_data(nick);
1233 static void
1234 parse_vcard_avatar(JabberStream *js, JabberPresence *presence, PurpleXmlNode *x)
1236 PurpleXmlNode *photo = purple_xmlnode_get_child(x, "photo");
1238 if (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("");
1246 static void
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");
1253 return;
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");
1262 int val;
1263 if (!code)
1264 continue;
1266 val = atoi(code);
1267 if (val == 0 || val < 0) {
1268 purple_debug_warning("jabber", "Ignoring bogus status code '%s'\n",
1269 code);
1270 continue;
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);
1300 /* XEPs */
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);
1309 /* Apple idle */
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;