Use GSList functions instead of manual iterations
[pidgin-git.git] / libpurple / protocols / jabber / presence.c
blob9fab30ad8709f1540b49b521f1b508d3dac6ae3e
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 (purple_strequal(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 /* check if there are any differences to the <presence> and send them in that case */
216 if (force || allowBuzz != js->allowBuzz || js->old_state != state ||
217 !purple_strequal(js->old_msg, stripped) || js->old_priority != priority ||
218 !purple_strequal(js->old_avatarhash, js->avatar_hash) || js->old_idle != js->idle) {
219 /* Need to update allowBuzz before creating the presence (with caps) */
220 js->allowBuzz = allowBuzz;
222 presence = jabber_presence_create_js(js, state, stripped, priority);
224 /* Per XEP-0153 4.1, we must always send the <x> */
225 x = purple_xmlnode_new_child(presence, "x");
226 purple_xmlnode_set_namespace(x, "vcard-temp:x:update");
228 * FIXME: Per XEP-0153 4.3.2 bullet 2, we must not publish our
229 * image hash if another resource has logged in and updated the
230 * vcard avatar. Requires changes in jabber_presence_parse.
232 if (js->vcard_fetched) {
233 /* Always publish a <photo>; it's empty if we have no image. */
234 photo = purple_xmlnode_new_child(x, "photo");
235 if (js->avatar_hash)
236 purple_xmlnode_insert_data(photo, js->avatar_hash, -1);
239 jabber_send(js, presence);
241 g_hash_table_foreach(js->chats, chats_send_presence_foreach, presence);
242 purple_xmlnode_free(presence);
244 /* update old values */
246 g_free(js->old_msg);
247 g_free(js->old_avatarhash);
248 js->old_msg = g_strdup(stripped);
249 js->old_avatarhash = g_strdup(js->avatar_hash);
250 js->old_state = state;
251 js->old_priority = priority;
252 js->old_idle = js->idle;
254 g_free(stripped);
256 /* next, check if there are any changes to the tune values */
257 if (purple_status_is_active(tune)) {
258 artist = purple_status_get_attr_string(tune, PURPLE_TUNE_ARTIST);
259 title = purple_status_get_attr_string(tune, PURPLE_TUNE_TITLE);
260 source = purple_status_get_attr_string(tune, PURPLE_TUNE_ALBUM);
261 uri = purple_status_get_attr_string(tune, PURPLE_TUNE_URL);
262 track = purple_status_get_attr_string(tune, PURPLE_TUNE_TRACK);
263 length = (!purple_status_get_attr_value(tune, PURPLE_TUNE_TIME)) ? -1 :
264 purple_status_get_attr_int(tune, PURPLE_TUNE_TIME);
267 if(!purple_strequal(artist, js->old_artist) || !purple_strequal(title, js->old_title) ||
268 !purple_strequal(source, js->old_source) || !purple_strequal(uri, js->old_uri) ||
269 !purple_strequal(track, js->old_track) || (length != js->old_length)) {
270 PurpleJabberTuneInfo tuneinfo = {
271 (char*)artist,
272 (char*)title,
273 (char*)source,
274 (char*)track,
275 length,
276 (char*)uri
278 jabber_tune_set(js->gc, &tuneinfo);
280 /* update old values */
281 g_free(js->old_artist);
282 g_free(js->old_title);
283 g_free(js->old_source);
284 g_free(js->old_uri);
285 g_free(js->old_track);
286 js->old_artist = g_strdup(artist);
287 js->old_title = g_strdup(title);
288 js->old_source = g_strdup(source);
289 js->old_uri = g_strdup(uri);
290 js->old_length = length;
291 js->old_track = g_strdup(track);
294 #undef CHANGED
296 jabber_presence_fake_to_self(js, status);
299 PurpleXmlNode *jabber_presence_create_js(JabberStream *js, JabberBuddyState state, const char *msg, int priority)
301 PurpleXmlNode *show, *status, *presence, *pri, *c;
302 const char *show_string = NULL;
303 #ifdef USE_VV
304 gboolean audio_enabled, video_enabled;
305 #endif
307 g_return_val_if_fail(js !=NULL, NULL);
309 presence = purple_xmlnode_new("presence");
311 if(state == JABBER_BUDDY_STATE_UNAVAILABLE)
312 purple_xmlnode_set_attrib(presence, "type", "unavailable");
313 else if(state != JABBER_BUDDY_STATE_ONLINE &&
314 state != JABBER_BUDDY_STATE_UNKNOWN &&
315 state != JABBER_BUDDY_STATE_ERROR)
316 show_string = jabber_buddy_state_get_show(state);
318 if(show_string) {
319 show = purple_xmlnode_new_child(presence, "show");
320 purple_xmlnode_insert_data(show, show_string, -1);
323 if(msg) {
324 status = purple_xmlnode_new_child(presence, "status");
325 purple_xmlnode_insert_data(status, msg, -1);
328 if(priority) {
329 char *pstr = g_strdup_printf("%d", priority);
330 pri = purple_xmlnode_new_child(presence, "priority");
331 purple_xmlnode_insert_data(pri, pstr, -1);
332 g_free(pstr);
335 /* if we are idle and not offline, include idle */
336 if (js->idle && state != JABBER_BUDDY_STATE_UNAVAILABLE) {
337 PurpleXmlNode *query = purple_xmlnode_new_child(presence, "query");
338 gchar seconds[10];
339 g_snprintf(seconds, 10, "%d", (int) (time(NULL) - js->idle));
341 purple_xmlnode_set_namespace(query, NS_LAST_ACTIVITY);
342 purple_xmlnode_set_attrib(query, "seconds", seconds);
345 /* JEP-0115 */
346 /* calculate hash */
347 jabber_caps_calculate_own_hash(js);
348 /* create xml */
349 c = purple_xmlnode_new_child(presence, "c");
350 purple_xmlnode_set_namespace(c, "http://jabber.org/protocol/caps");
351 purple_xmlnode_set_attrib(c, "node", CAPS0115_NODE);
352 purple_xmlnode_set_attrib(c, "hash", "sha-1");
353 purple_xmlnode_set_attrib(c, "ver", jabber_caps_get_own_hash(js));
355 #ifdef USE_VV
357 * MASSIVE HUGE DISGUSTING HACK
358 * This is a huge hack. As far as I can tell, Google Talk's gmail client
359 * doesn't bother to check the actual features we advertise; they
360 * just assume that if we specify a 'voice-v1' ext (ignoring that
361 * these are to be assigned no semantic value), we support receiving voice
362 * calls.
364 * Ditto for 'video-v1'.
366 audio_enabled = jabber_audio_enabled(js, NULL /* unused */);
367 video_enabled = jabber_video_enabled(js, NULL /* unused */);
369 if (audio_enabled && video_enabled)
370 purple_xmlnode_set_attrib(c, "ext", "voice-v1 camera-v1 video-v1");
371 else if (audio_enabled)
372 purple_xmlnode_set_attrib(c, "ext", "voice-v1");
373 else if (video_enabled)
374 purple_xmlnode_set_attrib(c, "ext", "camera-v1 video-v1");
375 #endif
377 return presence;
380 struct _jabber_add_permit {
381 PurpleConnection *gc;
382 JabberStream *js;
383 char *who;
386 static void authorize_add_cb(const char *message, gpointer data)
388 struct _jabber_add_permit *jap = data;
390 PURPLE_ASSERT_CONNECTION_IS_VALID(jap->gc);
392 jabber_presence_subscription_set(purple_connection_get_protocol_data(jap->gc),
393 jap->who, "subscribed");
395 g_free(jap->who);
396 g_free(jap);
399 static void deny_add_cb(const char *message, gpointer data)
401 struct _jabber_add_permit *jap = data;
403 PURPLE_ASSERT_CONNECTION_IS_VALID(jap->gc);
405 jabber_presence_subscription_set(purple_connection_get_protocol_data(jap->gc),
406 jap->who, "unsubscribed");
408 g_free(jap->who);
409 g_free(jap);
412 static void
413 jabber_vcard_parse_avatar(JabberStream *js, const char *from,
414 JabberIqType type, const char *id,
415 PurpleXmlNode *packet, gpointer blah)
417 JabberBuddy *jb = NULL;
418 PurpleXmlNode *vcard, *photo, *binval, *fn, *nick;
419 char *text;
421 if(!from)
422 return;
424 jb = jabber_buddy_find(js, from, TRUE);
426 js->pending_avatar_requests = g_slist_remove(js->pending_avatar_requests, jb);
428 if((vcard = purple_xmlnode_get_child(packet, "vCard")) ||
429 (vcard = purple_xmlnode_get_child_with_namespace(packet, "query", "vcard-temp"))) {
430 /* The logic here regarding the nickname and full name is copied from
431 * buddy.c:jabber_vcard_parse. */
432 gchar *nickname = NULL;
433 if ((fn = purple_xmlnode_get_child(vcard, "FN")))
434 nickname = purple_xmlnode_get_data(fn);
436 if ((nick = purple_xmlnode_get_child(vcard, "NICKNAME"))) {
437 char *tmp = purple_xmlnode_get_data(nick);
438 char *bare_jid = jabber_get_bare_jid(from);
439 if (tmp && strstr(bare_jid, tmp) == NULL) {
440 g_free(nickname);
441 nickname = tmp;
442 } else
443 g_free(tmp);
445 g_free(bare_jid);
448 if (nickname) {
449 purple_serv_got_alias(js->gc, from, nickname);
450 g_free(nickname);
453 if ((photo = purple_xmlnode_get_child(vcard, "PHOTO"))) {
454 guchar *data = NULL;
455 gchar *hash = NULL;
456 gsize size = 0;
458 if ((binval = purple_xmlnode_get_child(photo, "BINVAL")) &&
459 (text = purple_xmlnode_get_data(binval))) {
460 data = g_base64_decode(text, &size);
461 g_free(text);
463 if (data) {
464 hash = g_compute_checksum_for_data(G_CHECKSUM_SHA1, data,
465 size);
469 purple_buddy_icons_set_for_user(purple_connection_get_account(js->gc), from, data, size, hash);
471 g_free(hash);
476 typedef struct {
477 JabberStream *js;
478 JabberBuddy *jb;
479 char *from;
480 } JabberPresenceCapabilities;
482 static void
483 jabber_presence_set_capabilities(JabberCapsClientInfo *info, GList *exts,
484 JabberPresenceCapabilities *userdata)
486 JabberBuddyResource *jbr;
487 char *resource = strchr(userdata->from, '/');
489 if (resource)
490 resource += 1;
492 jbr = jabber_buddy_find_resource(userdata->jb, resource);
493 if (!jbr) {
494 g_free(userdata->from);
495 g_free(userdata);
496 g_list_free_full(exts, g_free);
497 return;
500 /* Any old jbr->caps.info is owned by the caps code */
501 g_list_free_full(jbr->caps.exts, g_free);
503 jbr->caps.info = info;
504 jbr->caps.exts = exts;
506 purple_protocol_got_media_caps(
507 purple_connection_get_account(userdata->js->gc),
508 userdata->from);
509 if (info == NULL)
510 goto out;
512 if (!jbr->commands_fetched && jabber_resource_has_capability(jbr, "http://jabber.org/protocol/commands")) {
513 JabberIq *iq = jabber_iq_new_query(userdata->js, JABBER_IQ_GET, NS_DISCO_ITEMS);
514 PurpleXmlNode *query = purple_xmlnode_get_child_with_namespace(iq->node, "query", NS_DISCO_ITEMS);
515 purple_xmlnode_set_attrib(iq->node, "to", userdata->from);
516 purple_xmlnode_set_attrib(query, "node", "http://jabber.org/protocol/commands");
517 jabber_iq_set_callback(iq, jabber_adhoc_disco_result_cb, NULL);
518 jabber_iq_send(iq);
520 jbr->commands_fetched = TRUE;
523 out:
524 g_free(userdata->from);
525 g_free(userdata);
528 static gboolean
529 handle_presence_chat(JabberStream *js, JabberPresence *presence, PurpleXmlNode *packet)
531 static int i = 1;
532 PurpleChatUserFlags flags = PURPLE_CHAT_USER_NONE;
533 JabberChat *chat = presence->chat;
535 if (presence->state == JABBER_BUDDY_STATE_ERROR) {
536 char *title, *msg = jabber_parse_error(js, packet, NULL);
538 if (!chat->conv) {
539 title = g_strdup_printf(_("Error joining chat %s"), presence->from);
540 purple_serv_got_join_chat_failed(js->gc, chat->components);
541 } else {
542 title = g_strdup_printf(_("Error in chat %s"), presence->from);
543 if (g_hash_table_size(chat->members) == 0)
544 purple_serv_got_chat_left(js->gc, chat->id);
546 purple_notify_error(js->gc, title, title, msg,
547 purple_request_cpar_from_connection(js->gc));
548 g_free(title);
549 g_free(msg);
551 if (g_hash_table_size(chat->members) == 0)
552 /* Only destroy the chat if the error happened while joining */
553 jabber_chat_destroy(chat);
554 return FALSE;
557 if (presence->type == JABBER_PRESENCE_AVAILABLE) {
558 const char *jid = NULL;
559 const char *affiliation = NULL;
560 const char *role = NULL;
561 gboolean is_our_resource = FALSE; /* Is the presence about us? */
562 JabberBuddyResource *jbr;
565 * XEP-0045 mandates the presence to include a resource (which is
566 * treated as the chat nick). Some non-compliant servers allow
567 * joining without a nick.
569 if (!presence->jid_from->resource)
570 return FALSE;
572 if (presence->chat_info.item) {
573 jid = purple_xmlnode_get_attrib(presence->chat_info.item, "jid");
574 affiliation = purple_xmlnode_get_attrib(presence->chat_info.item, "affiliation");
575 role = purple_xmlnode_get_attrib(presence->chat_info.item, "role");
578 if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(110)) ||
579 purple_strequal(presence->jid_from->resource, chat->handle) ||
580 purple_strequal(presence->to, jid))
581 is_our_resource = TRUE;
583 if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(201))) {
584 chat->config_dialog_type = PURPLE_REQUEST_ACTION;
585 chat->config_dialog_handle =
586 purple_request_action(js->gc,
587 _("Create New Room"),
588 _("Create New Room"),
589 _("You are creating a new room. Would"
590 " you like to configure it, or"
591 " accept the default settings?"),
592 /* Default Action */ 1,
593 purple_request_cpar_from_conversation(PURPLE_CONVERSATION(chat->conv)),
594 chat, 2,
595 _("_Configure Room"), G_CALLBACK(jabber_chat_request_room_configure),
596 _("_Accept Defaults"), G_CALLBACK(jabber_chat_create_instant_room));
599 if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(210))) {
600 /* server rewrote room-nick */
601 g_free(chat->handle);
602 chat->handle = g_strdup(presence->jid_from->resource);
605 if (purple_strequal(affiliation, "owner"))
606 flags |= PURPLE_CHAT_USER_FOUNDER;
607 if (role) {
608 if (purple_strequal(role, "moderator"))
609 flags |= PURPLE_CHAT_USER_OP;
610 else if (purple_strequal(role, "participant"))
611 flags |= PURPLE_CHAT_USER_VOICE;
614 if(!chat->conv) {
615 char *room_jid = g_strdup_printf("%s@%s", presence->jid_from->node, presence->jid_from->domain);
616 chat->id = i++;
617 chat->conv = purple_serv_got_joined_chat(js->gc, chat->id, room_jid);
618 purple_chat_conversation_set_nick(chat->conv, chat->handle);
620 jabber_chat_disco_traffic(chat);
621 g_free(room_jid);
624 jbr = jabber_buddy_track_resource(presence->jb, presence->jid_from->resource, presence->priority, presence->state, presence->status);
625 jbr->commands_fetched = TRUE;
627 jabber_chat_track_handle(chat, presence->jid_from->resource, jid, affiliation, role);
629 if(!jabber_chat_find_buddy(chat->conv, presence->jid_from->resource))
630 purple_chat_conversation_add_user(chat->conv, presence->jid_from->resource,
631 jid, flags, chat->joined > 0 && ((!presence->delayed) || (presence->sent > chat->joined)));
632 else
633 purple_chat_user_set_flags(purple_chat_conversation_find_user(chat->conv, presence->jid_from->resource),
634 flags);
636 if (is_our_resource && chat->joined == 0)
637 chat->joined = time(NULL);
639 } else if (presence->type == JABBER_PRESENCE_UNAVAILABLE) {
640 gboolean nick_change = FALSE;
641 gboolean kick = FALSE;
642 gboolean is_our_resource = FALSE; /* Is the presence about us? */
644 const char *jid = NULL;
646 /* If the chat nick is invalid, we haven't yet joined, or we've
647 * already left (it was probably us leaving after we closed the
648 * chat), we don't care.
650 if (!presence->jid_from->resource || !chat->conv || chat->left) {
651 if (chat->left &&
652 presence->jid_from->resource && chat->handle && purple_strequal(presence->jid_from->resource, chat->handle))
653 jabber_chat_destroy(chat);
654 return FALSE;
657 is_our_resource = purple_strequal(presence->jid_from->resource, chat->handle);
659 jabber_buddy_remove_resource(presence->jb, presence->jid_from->resource);
661 if (presence->chat_info.item)
662 jid = purple_xmlnode_get_attrib(presence->chat_info.item, "jid");
664 if (chat->muc) {
665 if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(110))) {
666 is_our_resource = TRUE;
667 chat->joined = 0;
670 if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(301))) {
671 /* XXX: We got banned. YAY! (No GIR, that's bad) */
675 if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(303))) {
676 const char *nick = NULL;
677 if (presence->chat_info.item)
678 nick = purple_xmlnode_get_attrib(presence->chat_info.item, "nick");
680 /* nick change */
681 if (!nick) {
682 purple_debug_warning("jabber", "Chat presence indicating a nick change, but no new nickname!\n");
683 } else {
684 nick_change = TRUE;
686 if (purple_strequal(presence->jid_from->resource, chat->handle)) {
687 /* Changing our own nickname */
688 g_free(chat->handle);
689 /* TODO: This should be resourceprep'd */
690 chat->handle = g_strdup(nick);
693 purple_chat_conversation_rename_user(chat->conv,
694 presence->jid_from->resource,
695 nick);
696 jabber_chat_remove_handle(chat,
697 presence->jid_from->resource);
701 if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(307))) {
702 /* Someone was kicked from the room */
703 const char *actor = NULL;
704 char *reason = NULL;
705 char *tmp;
707 kick = TRUE;
709 if (presence->chat_info.item) {
710 PurpleXmlNode *node;
712 node = purple_xmlnode_get_child(presence->chat_info.item, "actor");
713 if (node)
714 actor = purple_xmlnode_get_attrib(node, "jid");
715 node = purple_xmlnode_get_child(presence->chat_info.item, "reason");
716 if (node)
717 reason = purple_xmlnode_get_data(node);
720 if (reason == NULL)
721 reason = g_strdup(_("No reason"));
723 if (is_our_resource) {
724 if (actor)
725 tmp = g_strdup_printf(_("You have been kicked by %s: (%s)"),
726 actor, reason);
727 else
728 tmp = g_strdup_printf(_("You have been kicked: (%s)"),
729 reason);
730 } else {
731 if (actor)
732 tmp = g_strdup_printf(_("Kicked by %s (%s)"),
733 actor, reason);
734 else
735 tmp = g_strdup_printf(_("Kicked (%s)"),
736 reason);
739 g_free(presence->status);
740 presence->status = tmp;
742 g_free(reason);
745 if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(321))) {
746 /* XXX: removed due to an affiliation change */
749 if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(322))) {
750 /* XXX: removed because room is now members-only */
753 if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(332))) {
754 /* XXX: removed due to system shutdown */
759 * Possibly another connected resource of our JID (see XEP-0045
760 * v1.24 section 7.1.10) being disconnected. Should be
761 * distinguished by the item_jid.
762 * Also possibly works around bits of an Openfire bug. See
763 * #8319.
765 if (is_our_resource && jid && !purple_strequal(presence->to, jid)) {
766 /* TODO: When the above is a loop, this needs to still act
767 * sanely for all cases (this code is a little fragile). */
768 if (!kick && !nick_change)
769 /* Presumably, kicks and nick changes also affect us. */
770 is_our_resource = FALSE;
773 if(!nick_change) {
774 if (is_our_resource) {
775 if (kick) {
776 gchar *msg = g_strdup_printf("%s: %s",
777 presence->jid_from->resource,
778 presence->status);
779 purple_conversation_write_system_message(PURPLE_CONVERSATION(chat->conv), msg, 0);
782 purple_serv_got_chat_left(js->gc, chat->id);
783 jabber_chat_destroy(chat);
784 } else {
785 purple_chat_conversation_remove_user(chat->conv, presence->jid_from->resource,
786 presence->status);
787 jabber_chat_remove_handle(chat, presence->jid_from->resource);
792 return TRUE;
795 static gboolean
796 handle_presence_contact(JabberStream *js, JabberPresence *presence)
798 JabberBuddyResource *jbr;
799 PurpleAccount *account;
800 PurpleBuddy *b;
801 char *buddy_name;
802 PurpleIMConversation *im;
804 buddy_name = jabber_id_get_bare_jid(presence->jid_from);
806 account = purple_connection_get_account(js->gc);
807 b = purple_blist_find_buddy(account, buddy_name);
810 * Unbind/unlock from sending messages to a specific resource on
811 * presence changes. This is locked to a specific resource when
812 * receiving a message (in message.c).
814 im = purple_conversations_find_im_with_account(buddy_name, account);
815 if (im) {
816 purple_debug_info("jabber", "Changed conversation binding from %s to %s\n",
817 purple_conversation_get_name(PURPLE_CONVERSATION(im)), buddy_name);
818 purple_conversation_set_name(PURPLE_CONVERSATION(im), buddy_name);
821 if (b == NULL) {
822 if (presence->jb != js->user_jb) {
823 purple_debug_warning("jabber", "Got presence for unknown buddy %s on account %s (%p)\n",
824 buddy_name, purple_account_get_username(account), account);
825 g_free(buddy_name);
826 return FALSE;
827 } else {
828 /* this is a different resource of our own account. Resume even when this account isn't on our blist */
832 if (b && presence->vcard_avatar_hash) {
833 const char *ah = presence->vcard_avatar_hash[0] != '\0' ?
834 presence->vcard_avatar_hash : NULL;
835 const char *ah2 = purple_buddy_icons_get_checksum_for_user(b);
836 if (!purple_strequal(ah, ah2)) {
837 /* XXX this is a crappy way of trying to prevent
838 * someone from spamming us with presence packets
839 * and causing us to DoS ourselves...what we really
840 * need is a queue system that can throttle itself,
841 * but i'm too tired to write that right now */
842 if(!g_slist_find(js->pending_avatar_requests, presence->jb)) {
843 JabberIq *iq;
844 PurpleXmlNode *vcard;
846 js->pending_avatar_requests =
847 g_slist_prepend(js->pending_avatar_requests, presence->jb);
849 iq = jabber_iq_new(js, JABBER_IQ_GET);
850 purple_xmlnode_set_attrib(iq->node, "to", buddy_name);
851 vcard = purple_xmlnode_new_child(iq->node, "vCard");
852 purple_xmlnode_set_namespace(vcard, "vcard-temp");
854 jabber_iq_set_callback(iq, jabber_vcard_parse_avatar, NULL);
855 jabber_iq_send(iq);
860 if (presence->state == JABBER_BUDDY_STATE_ERROR ||
861 presence->type == JABBER_PRESENCE_UNAVAILABLE ||
862 presence->type == JABBER_PRESENCE_UNSUBSCRIBED) {
863 jabber_buddy_remove_resource(presence->jb, presence->jid_from->resource);
864 } else {
865 jbr = jabber_buddy_track_resource(presence->jb,
866 presence->jid_from->resource, presence->priority,
867 presence->state, presence->status);
868 jbr->idle = presence->idle ? time(NULL) - presence->idle : 0;
871 jbr = jabber_buddy_find_resource(presence->jb, NULL);
872 if (jbr) {
873 jabber_google_presence_incoming(js, buddy_name, jbr);
874 purple_protocol_got_user_status(account, buddy_name,
875 jabber_buddy_state_get_status_id(jbr->state),
876 "priority", jbr->priority,
877 "message", jbr->status,
878 NULL);
879 purple_protocol_got_user_idle(account, buddy_name,
880 jbr->idle, jbr->idle);
881 if (presence->nickname)
882 purple_serv_got_alias(js->gc, buddy_name, presence->nickname);
883 } else {
884 purple_protocol_got_user_status(account, buddy_name,
885 jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_UNAVAILABLE),
886 presence->status ? "message" : NULL, presence->status,
887 NULL);
889 g_free(buddy_name);
891 return TRUE;
894 void jabber_presence_parse(JabberStream *js, PurpleXmlNode *packet)
896 const char *type;
897 JabberBuddyResource *jbr = NULL;
898 gboolean signal_return, ret;
899 JabberPresence presence;
900 PurpleXmlNode *child;
902 memset(&presence, 0, sizeof(presence));
903 /* defaults */
904 presence.state = JABBER_BUDDY_STATE_UNKNOWN;
905 presence.sent = time(NULL);
906 /* interesting values */
907 presence.from = purple_xmlnode_get_attrib(packet, "from");
908 presence.to = purple_xmlnode_get_attrib(packet, "to");
909 type = purple_xmlnode_get_attrib(packet, "type");
910 presence.type = str_to_presence_type(type);
912 presence.jb = jabber_buddy_find(js, presence.from, TRUE);
913 g_return_if_fail(presence.jb != NULL);
915 presence.jid_from = jabber_id_new(presence.from);
916 if (presence.jid_from == NULL) {
917 purple_debug_error("jabber", "Ignoring presence with malformed 'from' "
918 "JID: %s\n", presence.from);
919 return;
922 signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(purple_connection_get_protocol(js->gc),
923 "jabber-receiving-presence", js->gc, type, presence.from, packet));
924 if (signal_return) {
925 goto out;
928 if (presence.jid_from->node)
929 presence.chat = jabber_chat_find(js, presence.jid_from->node,
930 presence.jid_from->domain);
931 g_free(presence.jb->error_msg);
932 presence.jb->error_msg = NULL;
934 if (presence.type == JABBER_PRESENCE_AVAILABLE) {
935 presence.state = JABBER_BUDDY_STATE_ONLINE;
936 } else if (presence.type == JABBER_PRESENCE_ERROR) {
937 /* TODO: Is this handled properly? Should it be treated as per-jbr? */
938 char *msg = jabber_parse_error(js, packet, NULL);
939 presence.state = JABBER_BUDDY_STATE_ERROR;
940 presence.jb->error_msg = msg ? msg : g_strdup(_("Unknown Error in presence"));
941 } else if (presence.type == JABBER_PRESENCE_SUBSCRIBE) {
942 /* TODO: Move to handle_subscribe() (so nick is extracted by the
943 * PresenceHandler */
944 struct _jabber_add_permit *jap = g_new0(struct _jabber_add_permit, 1);
945 gboolean onlist = FALSE;
946 PurpleAccount *account;
947 PurpleBuddy *buddy;
948 PurpleXmlNode *nick;
950 account = purple_connection_get_account(js->gc);
951 buddy = purple_blist_find_buddy(account, presence.from);
952 nick = purple_xmlnode_get_child_with_namespace(packet, "nick", "http://jabber.org/protocol/nick");
953 if (nick)
954 presence.nickname = purple_xmlnode_get_data(nick);
956 if (buddy) {
957 if ((presence.jb->subscription & (JABBER_SUB_TO | JABBER_SUB_PENDING)))
958 onlist = TRUE;
961 jap->gc = js->gc;
962 jap->who = g_strdup(presence.from);
963 jap->js = js;
965 purple_account_request_authorization(account, presence.from, NULL, presence.nickname,
966 NULL, onlist, authorize_add_cb, deny_add_cb, jap);
968 goto out;
969 } else if (presence.type == JABBER_PRESENCE_SUBSCRIBED) {
970 /* This case (someone has approved our subscribe request) is handled
971 * by the roster push the server sends along with this.
973 goto out;
974 } else if (presence.type == JABBER_PRESENCE_UNSUBSCRIBE) {
975 /* XXX I'm not sure this is the right way to handle this, it
976 * might be better to add "unsubscribe" to the presence status
977 * if lower down, but I'm not sure. */
978 /* they are unsubscribing from our presence, we don't care */
979 /* Well, maybe just a little, we might want/need to start
980 * acknowledging this (and the others) at some point. */
981 goto out;
982 } else if (presence.type == JABBER_PRESENCE_PROBE) {
983 purple_debug_warning("jabber", "Ignoring presence probe\n");
984 goto out;
985 } else if (presence.type == JABBER_PRESENCE_UNAVAILABLE) {
986 presence.state = JABBER_BUDDY_STATE_UNAVAILABLE;
987 } else if (presence.type == JABBER_PRESENCE_UNSUBSCRIBED) {
988 presence.state = JABBER_BUDDY_STATE_UNKNOWN;
989 } else {
990 purple_debug_warning("jabber", "Ignoring presence with invalid type "
991 "'%s'\n", type);
992 goto out;
995 for (child = packet->child; child; child = child->next) {
996 const char *xmlns;
997 char *key;
998 JabberPresenceHandler *pih;
999 if (child->type != PURPLE_XMLNODE_TYPE_TAG)
1000 continue;
1002 xmlns = purple_xmlnode_get_namespace(child);
1003 key = g_strdup_printf("%s %s", child->name, xmlns ? xmlns : "");
1004 pih = g_hash_table_lookup(presence_handlers, key);
1005 g_free(key);
1006 if (pih)
1007 pih(js, &presence, child);
1010 if (presence.delayed && presence.idle && presence.adjust_idle_for_delay) {
1011 /* Delayed and idle, so update idle time */
1012 presence.idle = presence.idle + (time(NULL) - presence.sent);
1015 /* TODO: Handle tracking jb(r) here? */
1017 if (presence.chat)
1018 ret = handle_presence_chat(js, &presence, packet);
1019 else
1020 ret = handle_presence_contact(js, &presence);
1021 if (!ret)
1022 goto out;
1024 if (presence.caps && presence.type == JABBER_PRESENCE_AVAILABLE) {
1025 /* handle Entity Capabilities (XEP-0115) */
1026 const char *node = purple_xmlnode_get_attrib(presence.caps, "node");
1027 const char *ver = purple_xmlnode_get_attrib(presence.caps, "ver");
1028 const char *hash = purple_xmlnode_get_attrib(presence.caps, "hash");
1029 const char *ext = purple_xmlnode_get_attrib(presence.caps, "ext");
1031 /* v1.3 uses: node, ver, and optionally ext.
1032 * v1.5 uses: node, ver, and hash. */
1033 if (node && *node && ver && *ver) {
1034 gchar **exts = ext && *ext ? g_strsplit(ext, " ", -1) : NULL;
1035 jbr = jabber_buddy_find_resource(presence.jb, presence.jid_from->resource);
1037 /* Look it up if we don't already have all this information */
1038 if (!jbr || !jbr->caps.info ||
1039 !purple_strequal(node, jbr->caps.info->tuple.node) ||
1040 !purple_strequal(ver, jbr->caps.info->tuple.ver) ||
1041 !purple_strequal(hash, jbr->caps.info->tuple.hash) ||
1042 !jabber_caps_exts_known(jbr->caps.info, (gchar **)exts)) {
1043 JabberPresenceCapabilities *userdata = g_new0(JabberPresenceCapabilities, 1);
1044 userdata->js = js;
1045 userdata->jb = presence.jb;
1046 userdata->from = g_strdup(presence.from);
1047 jabber_caps_get_info(js, presence.from, node, ver, hash, exts,
1048 (jabber_caps_get_info_cb)jabber_presence_set_capabilities,
1049 userdata);
1050 } else {
1051 if (exts)
1052 g_strfreev(exts);
1057 out:
1058 g_slist_free(presence.chat_info.codes);
1059 g_free(presence.status);
1060 g_free(presence.vcard_avatar_hash);
1061 g_free(presence.nickname);
1062 jabber_id_free(presence.jid_from);
1065 void jabber_presence_subscription_set(JabberStream *js, const char *who, const char *type)
1067 PurpleXmlNode *presence = purple_xmlnode_new("presence");
1069 purple_xmlnode_set_attrib(presence, "to", who);
1070 purple_xmlnode_set_attrib(presence, "type", type);
1072 jabber_send(js, presence);
1073 purple_xmlnode_free(presence);
1076 void purple_status_to_jabber(PurpleStatus *status, JabberBuddyState *state, char **msg, int *priority)
1078 const char *status_id = NULL;
1079 const char *formatted_msg = NULL;
1081 if(state) *state = JABBER_BUDDY_STATE_UNKNOWN;
1082 if(msg) *msg = NULL;
1083 if(priority) *priority = 0;
1085 if(!status) {
1086 if(state) *state = JABBER_BUDDY_STATE_UNAVAILABLE;
1087 } else {
1088 if(state) {
1089 status_id = purple_status_get_id(status);
1090 *state = jabber_buddy_status_id_get_state(status_id);
1093 if(msg) {
1094 formatted_msg = purple_status_get_attr_string(status, "message");
1096 /* if the message is blank, then there really isn't a message */
1097 if(formatted_msg && *formatted_msg)
1098 *msg = purple_markup_strip_html(formatted_msg);
1101 if(priority)
1102 *priority = purple_status_get_attr_int(status, "priority");
1106 /* Incoming presence handlers */
1107 static void
1108 parse_priority(JabberStream *js, JabberPresence *presence, PurpleXmlNode *priority)
1110 char *p = purple_xmlnode_get_data(priority);
1112 if (presence->priority != 0)
1113 purple_debug_warning("jabber", "presence stanza received with multiple "
1114 "priority children!?\n");
1116 if (p) {
1117 presence->priority = atoi(p);
1118 g_free(p);
1119 } else
1120 purple_debug_warning("jabber", "Empty <priority/> in presence!\n");
1123 static void
1124 parse_show(JabberStream *js, JabberPresence *presence, PurpleXmlNode *show)
1126 char *cdata;
1128 if (presence->type != JABBER_PRESENCE_AVAILABLE) {
1129 purple_debug_warning("jabber", "<show/> present on presence, but "
1130 "type is not default ('available')\n");
1131 return;
1134 cdata = purple_xmlnode_get_data(show);
1135 if (cdata) {
1136 presence->state = jabber_buddy_show_get_state(cdata);
1137 g_free(cdata);
1138 } else
1139 purple_debug_warning("jabber", "<show/> present on presence, but "
1140 "no contents!\n");
1143 static void
1144 parse_status(JabberStream *js, JabberPresence *presence, PurpleXmlNode *status)
1146 /* TODO: Check/track language attribute? */
1148 g_free(presence->status);
1149 presence->status = purple_xmlnode_get_data(status);
1152 static void
1153 parse_delay(JabberStream *js, JabberPresence *presence, PurpleXmlNode *delay)
1155 const char *stamp = purple_xmlnode_get_attrib(delay, "stamp");
1156 presence->delayed = TRUE;
1157 presence->sent = purple_str_to_time(stamp, TRUE, NULL, NULL, NULL);
1160 static void
1161 parse_apple_idle(JabberStream *js, JabberPresence *presence, PurpleXmlNode *x)
1163 PurpleXmlNode *since = purple_xmlnode_get_child(x, "idle-since");
1164 if (since) {
1165 char *stamp = purple_xmlnode_get_data_unescaped(since);
1166 if (stamp) {
1167 time_t tstamp = purple_str_to_time(stamp, TRUE, NULL, NULL, NULL);
1168 if (tstamp != 0) {
1169 presence->idle = time(NULL) - tstamp;
1170 presence->adjust_idle_for_delay = FALSE;
1171 if(presence->idle < 0) {
1172 purple_debug_warning("jabber", "Received bogus idle timestamp %s\n", stamp);
1173 presence->idle = 0;
1177 g_free(stamp);
1181 static void
1182 parse_idle(JabberStream *js, JabberPresence *presence, PurpleXmlNode *query)
1184 const gchar *seconds = purple_xmlnode_get_attrib(query, "seconds");
1185 if (seconds) {
1186 presence->idle = atoi(seconds);
1187 presence->adjust_idle_for_delay = TRUE;
1188 if (presence->idle < 0) {
1189 purple_debug_warning("jabber", "Received bogus idle time %s\n", seconds);
1190 presence->idle = 0;
1195 static void
1196 parse_caps(JabberStream *js, JabberPresence *presence, PurpleXmlNode *c)
1198 /* TODO: Move the rest of the caps handling in here, after changing the
1199 * the "do we have details about this (node, ver) and exts" to not
1200 * require the jbr to be present (since that happens later).
1202 presence->caps = c;
1205 static void
1206 parse_nickname(JabberStream *js, JabberPresence *presence, PurpleXmlNode *nick)
1208 g_free(presence->nickname);
1209 presence->nickname = purple_xmlnode_get_data(nick);
1212 static void
1213 parse_vcard_avatar(JabberStream *js, JabberPresence *presence, PurpleXmlNode *x)
1215 PurpleXmlNode *photo = purple_xmlnode_get_child(x, "photo");
1217 if (photo) {
1218 char *hash_tmp = purple_xmlnode_get_data(photo);
1219 g_free(presence->vcard_avatar_hash);
1220 presence->vcard_avatar_hash =
1221 hash_tmp ? hash_tmp : g_strdup("");
1225 static void
1226 parse_muc_user(JabberStream *js, JabberPresence *presence, PurpleXmlNode *x)
1228 PurpleXmlNode *status;
1230 if (presence->chat == NULL) {
1231 purple_debug_warning("jabber", "Ignoring MUC gloop on non-MUC presence\n");
1232 return;
1235 if (presence->chat->conv == NULL)
1236 presence->chat->muc = TRUE;
1238 for (status = purple_xmlnode_get_child(x, "status"); status;
1239 status = purple_xmlnode_get_next_twin(status)) {
1240 const char *code = purple_xmlnode_get_attrib(status, "code");
1241 int val;
1242 if (!code)
1243 continue;
1245 val = atoi(code);
1246 if (val == 0 || val < 0) {
1247 purple_debug_warning("jabber", "Ignoring bogus status code '%s'\n",
1248 code);
1249 continue;
1252 presence->chat_info.codes = g_slist_prepend(presence->chat_info.codes, GINT_TO_POINTER(val));
1255 presence->chat_info.item = purple_xmlnode_get_child(x, "item");
1258 void jabber_presence_register_handler(const char *node, const char *xmlns,
1259 JabberPresenceHandler *handler)
1262 * This is valid because nodes nor namespaces cannot have spaces in them
1263 * (see http://www.w3.org/TR/2006/REC-xml-20060816/ and
1264 * http://www.w3.org/TR/REC-xml-names/)
1266 char *key = g_strdup_printf("%s %s", node, xmlns);
1267 g_hash_table_replace(presence_handlers, key, handler);
1270 void jabber_presence_init(void)
1272 presence_handlers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1274 /* Core RFC things */
1275 jabber_presence_register_handler("priority", "jabber:client", parse_priority);
1276 jabber_presence_register_handler("show", "jabber:client", parse_show);
1277 jabber_presence_register_handler("status", "jabber:client", parse_status);
1279 /* XEPs */
1280 jabber_presence_register_handler("c", "http://jabber.org/protocol/caps", parse_caps);
1281 jabber_presence_register_handler("delay", NS_DELAYED_DELIVERY, parse_delay);
1282 jabber_presence_register_handler("nick", "http://jabber.org/protocol/nick", parse_nickname);
1283 jabber_presence_register_handler("query", NS_LAST_ACTIVITY, parse_idle);
1284 jabber_presence_register_handler("x", NS_DELAYED_DELIVERY_LEGACY, parse_delay);
1285 jabber_presence_register_handler("x", "http://jabber.org/protocol/muc#user", parse_muc_user);
1286 jabber_presence_register_handler("x", "vcard-temp:x:update", parse_vcard_avatar);
1288 /* Apple idle */
1289 jabber_presence_register_handler("x", NS_APPLE_IDLE, parse_apple_idle);
1292 void jabber_presence_uninit(void)
1294 g_hash_table_destroy(presence_handlers);
1295 presence_handlers = NULL;