comparison libpurple/protocols/jabber/presence.c @ 29560:13f320cde14f

jabber: Heavily refactor jabber_presence_parse(). It's still not short enough. This also fixes building (jabber_presence_uninit() snuck in to my previous changes). Anyway, this has been lightly tested, but should be a lot cleaner going forward, hopefully.
author Paul Aurich <paul@darkrain42.org>
date Tue, 09 Mar 2010 22:44:59 +0000
parents b0cb194dc139
children ffc1f997cb91
comparison
equal deleted inserted replaced
29559:93d32ecf3186 29560:13f320cde14f
41 #include "adhoccommands.h" 41 #include "adhoccommands.h"
42 42
43 #include "usermood.h" 43 #include "usermood.h"
44 #include "usertune.h" 44 #include "usertune.h"
45 45
46 static GHashTable *presence_handlers = NULL;
47
48 static const struct {
49 const char *name;
50 JabberPresenceType type;
51 } jabber_presence_types[] = {
52 { "error", JABBER_PRESENCE_ERROR },
53 { "probe", JABBER_PRESENCE_PROBE },
54 { "unavailable", JABBER_PRESENCE_UNAVAILABLE },
55 { "subscribe", JABBER_PRESENCE_SUBSCRIBE },
56 { "subscribed", JABBER_PRESENCE_SUBSCRIBED },
57 { "unsubscribe", JABBER_PRESENCE_UNSUBSCRIBE },
58 { "unsubscribed", JABBER_PRESENCE_UNSUBSCRIBED }
59 /* { NULL, JABBER_PRESENCE_AVAILABLE } the default */
60 };
61
62 static JabberPresenceType
63 str_to_presence_type(const char *type)
64 {
65 int i;
66
67 if (type == NULL)
68 return JABBER_PRESENCE_AVAILABLE;
69
70 for (i = 0; i < G_N_ELEMENTS(jabber_presence_types); ++i)
71 if (g_str_equal(type, jabber_presence_types[i].name))
72 return jabber_presence_types[i].type;
73
74 purple_debug_warning("jabber", "Unknown presence type '%s'\n", type);
75 return JABBER_PRESENCE_AVAILABLE;
76 }
46 77
47 static void chats_send_presence_foreach(gpointer key, gpointer val, 78 static void chats_send_presence_foreach(gpointer key, gpointer val,
48 gpointer user_data) 79 gpointer user_data)
49 { 80 {
50 JabberChat *chat = val; 81 JabberChat *chat = val;
474 jbr->caps.exts = exts; 505 jbr->caps.exts = exts;
475 506
476 purple_prpl_got_media_caps( 507 purple_prpl_got_media_caps(
477 purple_connection_get_account(userdata->js->gc), 508 purple_connection_get_account(userdata->js->gc),
478 userdata->from); 509 userdata->from);
479
480 if (info == NULL) 510 if (info == NULL)
481 goto out; 511 goto out;
482 512
483 if (!jbr->commands_fetched && jabber_resource_has_capability(jbr, "http://jabber.org/protocol/commands")) { 513 if (!jbr->commands_fetched && jabber_resource_has_capability(jbr, "http://jabber.org/protocol/commands")) {
484 JabberIq *iq = jabber_iq_new_query(userdata->js, JABBER_IQ_GET, NS_DISCO_ITEMS); 514 JabberIq *iq = jabber_iq_new_query(userdata->js, JABBER_IQ_GET, NS_DISCO_ITEMS);
506 out: 536 out:
507 g_free(userdata->from); 537 g_free(userdata->from);
508 g_free(userdata); 538 g_free(userdata);
509 } 539 }
510 540
541 gboolean
542 handle_presence_chat(JabberStream *js, JabberPresence *presence, xmlnode *packet)
543 {
544 static int i = 1;
545 PurpleConvChatBuddyFlags flags = PURPLE_CBFLAGS_NONE;
546 JabberChat *chat = presence->chat;
547
548 if (presence->state == JABBER_BUDDY_STATE_ERROR) {
549 char *title, *msg = jabber_parse_error(js, packet, NULL);
550
551 if (!chat->conv) {
552 title = g_strdup_printf(_("Error joining chat %s"), presence->from);
553 purple_serv_got_join_chat_failed(js->gc, chat->components);
554 } else {
555 title = g_strdup_printf(_("Error in chat %s"), presence->from);
556 if (g_hash_table_size(chat->members) == 0)
557 serv_got_chat_left(js->gc, chat->id);
558 }
559 purple_notify_error(js->gc, title, title, msg);
560 g_free(title);
561 g_free(msg);
562
563 if (g_hash_table_size(chat->members) == 0)
564 /* Only destroy the chat if the error happened while joining */
565 jabber_chat_destroy(chat);
566 return FALSE;
567 }
568
569 if (presence->type == JABBER_PRESENCE_AVAILABLE) {
570 const char *jid = NULL;
571 const char *affiliation = NULL;
572 const char *role = NULL;
573 gboolean is_our_resource = FALSE; /* Is the presence about us? */
574 JabberBuddyResource *jbr;
575
576 /*
577 * XEP-0045 mandates the presence to include a resource (which is
578 * treated as the chat nick). Some non-compliant servers allow
579 * joining without a nick.
580 */
581 if (!presence->jid_from->resource)
582 return FALSE;
583
584 if (presence->chat_info.item) {
585 jid = xmlnode_get_attrib(presence->chat_info.item, "jid");
586 affiliation = xmlnode_get_attrib(presence->chat_info.item, "affiliation");
587 role = xmlnode_get_attrib(presence->chat_info.item, "role");
588 }
589
590 if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(110)))
591 is_our_resource = TRUE;
592
593 if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(201))) {
594 chat->config_dialog_type = PURPLE_REQUEST_ACTION;
595 chat->config_dialog_handle =
596 purple_request_action(js->gc,
597 _("Create New Room"),
598 _("Create New Room"),
599 _("You are creating a new room. Would"
600 " you like to configure it, or"
601 " accept the default settings?"),
602 /* Default Action */ 1,
603 purple_connection_get_account(js->gc), NULL, chat->conv,
604 chat, 2,
605 _("_Configure Room"), G_CALLBACK(jabber_chat_request_room_configure),
606 _("_Accept Defaults"), G_CALLBACK(jabber_chat_create_instant_room));
607 }
608
609 if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(210))) {
610 /* server rewrote room-nick */
611 g_free(chat->handle);
612 chat->handle = g_strdup(presence->jid_from->resource);
613 }
614
615 if (purple_strequal(affiliation, "owner"))
616 flags |= PURPLE_CBFLAGS_FOUNDER;
617 if (role) {
618 if (g_str_equal(role, "moderator"))
619 flags |= PURPLE_CBFLAGS_OP;
620 else if (g_str_equal(role, "participant"))
621 flags |= PURPLE_CBFLAGS_VOICE;
622 }
623
624 if(!chat->conv) {
625 char *room_jid = g_strdup_printf("%s@%s", presence->jid_from->node, presence->jid_from->domain);
626 chat->id = i++;
627 chat->conv = serv_got_joined_chat(js->gc, chat->id, room_jid);
628 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(chat->conv), chat->handle);
629
630 jabber_chat_disco_traffic(chat);
631 g_free(room_jid);
632 }
633
634 jbr = jabber_buddy_track_resource(presence->jb, presence->jid_from->resource, presence->priority, presence->state, presence->status);
635 jbr->commands_fetched = TRUE;
636
637 jabber_chat_track_handle(chat, presence->jid_from->resource, jid, affiliation, role);
638
639 if(!jabber_chat_find_buddy(chat->conv, presence->jid_from->resource))
640 purple_conv_chat_add_user(PURPLE_CONV_CHAT(chat->conv), presence->jid_from->resource,
641 jid, flags, !presence->delayed);
642 else
643 purple_conv_chat_user_set_flags(PURPLE_CONV_CHAT(chat->conv), presence->jid_from->resource,
644 flags);
645 } else if (presence->type == JABBER_PRESENCE_UNAVAILABLE) {
646 gboolean nick_change = FALSE;
647 gboolean kick = FALSE;
648 gboolean is_our_resource = FALSE; /* Is the presence about us? */
649
650 const char *jid = NULL;
651
652 /* If the chat nick is invalid, we haven't yet joined, or we've
653 * already left (it was probably us leaving after we closed the
654 * chat), we don't care.
655 */
656 if (!presence->jid_from->resource || !chat->conv || chat->left) {
657 if (chat->left &&
658 presence->jid_from->resource && chat->handle && !strcmp(presence->jid_from->resource, chat->handle))
659 jabber_chat_destroy(chat);
660 return FALSE;
661 }
662
663 is_our_resource = (0 == g_utf8_collate(presence->jid_from->resource, chat->handle));
664
665 jabber_buddy_remove_resource(presence->jb, presence->jid_from->resource);
666
667 if (presence->chat_info.item)
668 jid = xmlnode_get_attrib(presence->chat_info.item, "jid");
669
670 if (chat->muc) {
671 if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(110)))
672 is_our_resource = TRUE;
673
674 if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(301))) {
675 /* XXX: We got banned. YAY! (No GIR, that's bad) */
676 }
677
678
679 if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(303))) {
680 const char *nick = NULL;
681 if (presence->chat_info.item)
682 nick = xmlnode_get_attrib(presence->chat_info.item, "nick");
683
684 /* nick change */
685 if (nick) {
686 purple_debug_warning("jabber", "Chat presence indicating a nick change, but no new nickname!\n");
687 } else {
688 nick_change = TRUE;
689
690 if (g_str_equal(presence->jid_from->resource, chat->handle)) {
691 /* Changing our own nickname */
692 g_free(chat->handle);
693 chat->handle = g_strdup(nick);
694 }
695
696 purple_conv_chat_rename_user(PURPLE_CONV_CHAT(chat->conv),
697 presence->jid_from->resource,
698 nick);
699 jabber_chat_remove_handle(chat,
700 presence->jid_from->resource);
701 }
702 }
703
704 if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(307))) {
705 /* Someone was kicked from the room */
706 const char *actor = NULL;
707 char *reason = NULL;
708 char *tmp;
709
710 kick = TRUE;
711
712 if (presence->chat_info.item) {
713 xmlnode *node;
714
715 node = xmlnode_get_child(presence->chat_info.item, "actor");
716 if (node)
717 actor = xmlnode_get_attrib(node, "jid");
718 node = xmlnode_get_child(presence->chat_info.item, "reason");
719 if (node)
720 reason = xmlnode_get_data(node);
721 }
722
723 if (reason == NULL)
724 reason = g_strdup(_("No reason"));
725
726 if (is_our_resource) {
727 if (actor)
728 tmp = g_strdup_printf(_("You have been kicked by %s: (%s)"),
729 actor, reason);
730 else
731 tmp = g_strdup_printf(_("You have been kicked: (%s)"),
732 reason);
733 } else {
734 if (actor)
735 tmp = g_strdup_printf(_("Kicked by %s (%s)"),
736 actor, reason);
737 else
738 tmp = g_strdup_printf(_("Kicked (%s)"),
739 reason);
740 }
741
742 g_free(presence->status);
743 presence->status = tmp;
744
745 g_free(reason);
746 }
747
748 if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(321))) {
749 /* XXX: removed due to an affiliation change */
750 }
751
752 if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(322))) {
753 /* XXX: removed because room is now members-only */
754 }
755
756 if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(332))) {
757 /* XXX: removed due to system shutdown */
758 }
759 }
760
761 /*
762 * Possibly another connected resource of our JID (see XEP-0045
763 * v1.24 section 7.1.10) being disconnected. Should be
764 * distinguished by the item_jid.
765 * Also possibly works around bits of an Openfire bug. See
766 * #8319.
767 */
768 if (is_our_resource && jid && !purple_strequal(presence->to, jid)) {
769 /* TODO: When the above is a loop, this needs to still act
770 * sanely for all cases (this code is a little fragile). */
771 if (!kick && !nick_change)
772 /* Presumably, kicks and nick changes also affect us. */
773 is_our_resource = FALSE;
774 }
775
776 if(!nick_change) {
777 if (is_our_resource) {
778 if (kick)
779 purple_conv_chat_write(PURPLE_CONV_CHAT(chat->conv), presence->jid_from->resource,
780 presence->status, PURPLE_MESSAGE_SYSTEM, time(NULL));
781
782 serv_got_chat_left(js->gc, chat->id);
783 jabber_chat_destroy(chat);
784 } else {
785 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(chat->conv), presence->jid_from->resource,
786 presence->status);
787 jabber_chat_remove_handle(chat, presence->jid_from->resource);
788 }
789 }
790 }
791
792 return TRUE;
793 }
794
795 gboolean
796 handle_presence_contact(JabberStream *js, JabberPresence *presence)
797 {
798 JabberBuddyResource *jbr;
799 PurpleAccount *account;
800 PurpleBuddy *b;
801 char *buddy_name;
802 PurpleConversation *conv;
803
804 buddy_name = jabber_id_get_bare_jid(presence->jid_from);
805
806 account = purple_connection_get_account(js->gc);
807 b = purple_find_buddy(account, buddy_name);
808
809 /*
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).
813 */
814 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
815 buddy_name, account);
816 if (conv) {
817 purple_debug_info("jabber", "Changed conversation binding from %s to %s\n",
818 purple_conversation_get_name(conv), buddy_name);
819 purple_conversation_set_name(conv, buddy_name);
820 }
821
822 if (b == NULL) {
823 if (presence->jb != js->user_jb) {
824 purple_debug_warning("jabber", "Got presence for unknown buddy %s on account %s (%p)\n",
825 buddy_name, purple_account_get_username(account), account);
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 */
829 }
830 }
831
832 if(b && presence->vcard_avatar_hash) {
833 const char *avatar_hash2 = purple_buddy_icons_get_checksum_for_user(b);
834 if(!avatar_hash2 || strcmp(presence->vcard_avatar_hash, avatar_hash2)) {
835 JabberIq *iq;
836 xmlnode *vcard;
837
838 /* XXX this is a crappy way of trying to prevent
839 * someone from spamming us with presence packets
840 * and causing us to DoS ourselves...what we really
841 * need is a queue system that can throttle itself,
842 * but i'm too tired to write that right now */
843 if(!g_slist_find(js->pending_avatar_requests, presence->jb)) {
844
845 js->pending_avatar_requests = g_slist_prepend(js->pending_avatar_requests, presence->jb);
846
847 iq = jabber_iq_new(js, JABBER_IQ_GET);
848 xmlnode_set_attrib(iq->node, "to", buddy_name);
849 vcard = xmlnode_new_child(iq->node, "vCard");
850 xmlnode_set_namespace(vcard, "vcard-temp");
851
852 jabber_iq_set_callback(iq, jabber_vcard_parse_avatar, NULL);
853 jabber_iq_send(iq);
854 }
855 }
856 }
857
858 if (presence->state == JABBER_BUDDY_STATE_ERROR ||
859 presence->type == JABBER_PRESENCE_UNAVAILABLE ||
860 presence->type == JABBER_PRESENCE_UNSUBSCRIBED) {
861 jabber_buddy_remove_resource(presence->jb, presence->jid_from->resource);
862 } else {
863 jbr = jabber_buddy_track_resource(presence->jb,
864 presence->jid_from->resource, presence->priority,
865 presence->state, presence->status);
866 jbr->idle = presence->idle ? time(NULL) - presence->idle : 0;
867 }
868
869 jbr = jabber_buddy_find_resource(presence->jb, NULL);
870 if (jbr) {
871 jabber_google_presence_incoming(js, buddy_name, jbr);
872 purple_prpl_got_user_status(account, buddy_name,
873 jabber_buddy_state_get_status_id(jbr->state),
874 "priority", jbr->priority,
875 "message", jbr->status,
876 NULL);
877 purple_prpl_got_user_idle(account, buddy_name,
878 jbr->idle, jbr->idle);
879 if (presence->nickname)
880 serv_got_alias(js->gc, buddy_name, presence->nickname);
881 } else {
882 purple_prpl_got_user_status(account, buddy_name,
883 jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_UNAVAILABLE),
884 presence->status ? "message" : NULL, presence->status,
885 NULL);
886 }
887 g_free(buddy_name);
888
889 return TRUE;
890 }
891
511 void jabber_presence_parse(JabberStream *js, xmlnode *packet) 892 void jabber_presence_parse(JabberStream *js, xmlnode *packet)
512 { 893 {
513 const char *from;
514 const char *type; 894 const char *type;
515 char *status = NULL; 895 JabberBuddyResource *jbr = NULL;
516 int priority = 0; 896 gboolean signal_return, ret;
517 JabberID *jid; 897 JabberPresence presence;
518 JabberChat *chat; 898 xmlnode *child;
519 JabberBuddy *jb; 899
520 JabberBuddyResource *jbr = NULL, *found_jbr = NULL; 900 memset(&presence, 0, sizeof(presence));
521 PurpleConvChatBuddyFlags flags = PURPLE_CBFLAGS_NONE; 901 /* defaults */
522 gboolean delayed = FALSE; 902 presence.state = JABBER_BUDDY_STATE_UNKNOWN;
523 const gchar *stamp = NULL; /* from <delayed/> element */ 903 presence.sent = time(NULL);
524 PurpleAccount *account; 904 /* interesting values */
525 PurpleBuddy *b = NULL; 905 presence.from = xmlnode_get_attrib(packet, "from");
526 char *buddy_name; 906 presence.to = xmlnode_get_attrib(packet, "to");
527 JabberBuddyState state = JABBER_BUDDY_STATE_UNKNOWN;
528 xmlnode *y;
529 char *avatar_hash = NULL;
530 xmlnode *caps = NULL;
531 int idle = 0;
532 gchar *nickname = NULL;
533 gboolean signal_return;
534
535 from = xmlnode_get_attrib(packet, "from");
536 type = xmlnode_get_attrib(packet, "type"); 907 type = xmlnode_get_attrib(packet, "type");
537 908 presence.type = str_to_presence_type(type);
538 jb = jabber_buddy_find(js, from, TRUE); 909
539 g_return_if_fail(jb != NULL); 910 presence.jb = jabber_buddy_find(js, presence.from, TRUE);
911 g_return_if_fail(presence.jb != NULL);
912
913 presence.jid_from = jabber_id_new(presence.from);
914 if (presence.jid_from == NULL) {
915 purple_debug_error("jabber", "Ignoring presence with malformed 'from' "
916 "JID: %s\n", presence.from);
917 return;
918 }
540 919
541 signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(purple_connection_get_prpl(js->gc), 920 signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(purple_connection_get_prpl(js->gc),
542 "jabber-receiving-presence", js->gc, type, from, packet)); 921 "jabber-receiving-presence", js->gc, type, presence.from, packet));
543 if (signal_return) 922 if (signal_return) {
544 return; 923 goto out;
545 924 }
546 account = purple_connection_get_account(js->gc); 925
547 926 presence.chat = jabber_chat_find(js, presence.jid_from->node,
548 jid = jabber_id_new(from); 927 presence.jid_from->domain);
549 if (jid == NULL) { 928 if(presence.jb->error_msg) {
550 purple_debug_error("jabber", "Ignoring presence with malformed 'from' " 929 g_free(presence.jb->error_msg);
551 "JID: %s\n", from); 930 presence.jb->error_msg = NULL;
552 return; 931 }
553 } 932
554 933 if (presence.type == JABBER_PRESENCE_AVAILABLE) {
555 if(jb->error_msg) { 934 presence.state = JABBER_BUDDY_STATE_ONLINE;
556 g_free(jb->error_msg); 935 } else if (presence.type == JABBER_PRESENCE_ERROR) {
557 jb->error_msg = NULL; 936 /* TODO: Is this handled properly? Should it be treated as per-jbr? */
558 }
559
560 if (type == NULL) {
561 xmlnode *show;
562 char *show_data = NULL;
563
564 state = JABBER_BUDDY_STATE_ONLINE;
565
566 show = xmlnode_get_child(packet, "show");
567 if (show) {
568 show_data = xmlnode_get_data(show);
569 if (show_data) {
570 state = jabber_buddy_show_get_state(show_data);
571 g_free(show_data);
572 } else
573 purple_debug_warning("jabber", "<show/> present on presence, "
574 "but no contents!\n");
575 }
576 } else if (g_str_equal(type, "error")) {
577 char *msg = jabber_parse_error(js, packet, NULL); 937 char *msg = jabber_parse_error(js, packet, NULL);
578 938 presence.state = JABBER_BUDDY_STATE_ERROR;
579 state = JABBER_BUDDY_STATE_ERROR; 939 presence.jb->error_msg = msg ? msg : g_strdup(_("Unknown Error in presence"));
580 jb->error_msg = msg ? msg : g_strdup(_("Unknown Error in presence")); 940 } else if (presence.type == JABBER_PRESENCE_SUBSCRIBE) {
581 } else if (g_str_equal(type, "subscribe")) { 941 /* TODO: Move to handle_subscribe() (so nick is extracted by the
942 * PresenceHandler */
582 struct _jabber_add_permit *jap = g_new0(struct _jabber_add_permit, 1); 943 struct _jabber_add_permit *jap = g_new0(struct _jabber_add_permit, 1);
583 gboolean onlist = FALSE; 944 gboolean onlist = FALSE;
945 PurpleAccount *account;
584 PurpleBuddy *buddy; 946 PurpleBuddy *buddy;
585 JabberBuddy *jb = NULL;
586 xmlnode *nick; 947 xmlnode *nick;
587 948
588 buddy = purple_find_buddy(account, from); 949 account = purple_connection_get_account(js->gc);
950 buddy = purple_find_buddy(account, presence.from);
589 nick = xmlnode_get_child_with_namespace(packet, "nick", "http://jabber.org/protocol/nick"); 951 nick = xmlnode_get_child_with_namespace(packet, "nick", "http://jabber.org/protocol/nick");
590 if (nick) 952 if (nick)
591 nickname = xmlnode_get_data(nick); 953 presence.nickname = xmlnode_get_data(nick);
592 954
593 if (buddy) { 955 if (buddy) {
594 jb = jabber_buddy_find(js, from, TRUE); 956 if ((presence.jb->subscription & (JABBER_SUB_TO | JABBER_SUB_PENDING)))
595 if ((jb->subscription & (JABBER_SUB_TO | JABBER_SUB_PENDING)))
596 onlist = TRUE; 957 onlist = TRUE;
597 } 958 }
598 959
599 jap->gc = js->gc; 960 jap->gc = js->gc;
600 jap->who = g_strdup(from); 961 jap->who = g_strdup(presence.from);
601 jap->js = js; 962 jap->js = js;
602 963
603 purple_account_request_authorization(account, from, NULL, nickname, 964 purple_account_request_authorization(account, presence.from, NULL, presence.nickname,
604 NULL, onlist, authorize_add_cb, deny_add_cb, jap); 965 NULL, onlist, authorize_add_cb, deny_add_cb, jap);
605 966
606 g_free(nickname); 967 goto out;
607 jabber_id_free(jid); 968 } else if (presence.type == JABBER_PRESENCE_SUBSCRIBED) {
608 return; 969 /* This case (someone has approved our subscribe request) is handled
609 } else if (g_str_equal(type, "subscribed")) { 970 * by the roster push the server sends along with this.
610 /* we've been allowed to see their presence, but we don't care */ 971 */
611 jabber_id_free(jid); 972 goto out;
612 return; 973 } else if (presence.type == JABBER_PRESENCE_UNSUBSCRIBE) {
613 } else if (g_str_equal(type, "unsubscribe")) {
614 /* XXX I'm not sure this is the right way to handle this, it 974 /* XXX I'm not sure this is the right way to handle this, it
615 * might be better to add "unsubscribe" to the presence status 975 * might be better to add "unsubscribe" to the presence status
616 * if lower down, but I'm not sure. */ 976 * if lower down, but I'm not sure. */
617 /* they are unsubscribing from our presence, we don't care */ 977 /* they are unsubscribing from our presence, we don't care */
618 /* Well, maybe just a little, we might want/need to start 978 /* Well, maybe just a little, we might want/need to start
619 * acknowledging this (and the others) at some point. */ 979 * acknowledging this (and the others) at some point. */
620 jabber_id_free(jid); 980 goto out;
621 return; 981 } else if (presence.type == JABBER_PRESENCE_PROBE) {
622 } else if (g_str_equal(type, "probe")) {
623 purple_debug_warning("jabber", "Ignoring presence probe\n"); 982 purple_debug_warning("jabber", "Ignoring presence probe\n");
624 jabber_id_free(jid); 983 goto out;
625 return; 984 } else if (presence.type == JABBER_PRESENCE_UNAVAILABLE) {
626 } else if (g_str_equal(type, "unavailable")) { 985 presence.state = JABBER_BUDDY_STATE_UNAVAILABLE;
627 state = JABBER_BUDDY_STATE_UNAVAILABLE; 986 } else if (presence.type == JABBER_PRESENCE_UNSUBSCRIBED) {
628 } else if (g_str_equal(type, "unsubscribed")) { 987 presence.state = JABBER_BUDDY_STATE_UNKNOWN;
629 state = JABBER_BUDDY_STATE_UNKNOWN;
630 } else { 988 } else {
631 purple_debug_warning("jabber", "Ignoring presence with invalid type " 989 purple_debug_warning("jabber", "Ignoring presence with invalid type "
632 "'%s'\n", type); 990 "'%s'\n", type);
633 jabber_id_free(jid); 991 goto out;
634 return; 992 }
635 } 993
636 994 for (child = packet->child; child; child = child->next) {
637 995 char *key;
638 for(y = packet->child; y; y = y->next) { 996 JabberPresenceHandler *pih;
639 const char *xmlns; 997 if (child->type != XMLNODE_TYPE_TAG)
640 if(y->type != XMLNODE_TYPE_TAG)
641 continue; 998 continue;
642 xmlns = xmlnode_get_namespace(y); 999
643 1000 key = g_strdup_printf("%s %s", child->name, xmlnode_get_namespace(child));
644 if(!strcmp(y->name, "status")) { 1001 pih = g_hash_table_lookup(presence_handlers, key);
645 g_free(status); 1002 g_free(key);
646 status = xmlnode_get_data(y); 1003 if (pih)
647 } else if(!strcmp(y->name, "priority")) { 1004 pih(js, &presence, child);
648 char *p = xmlnode_get_data(y); 1005 }
649 if(p) { 1006
650 priority = atoi(p); 1007 if (presence.delayed && presence.idle) {
651 g_free(p); 1008 /* Delayed and idle, so update idle time */
652 } 1009 presence.idle = presence.idle + (time(NULL) - presence.sent);
653 } else if(xmlns == NULL) { 1010 }
654 /* The rest of the cases used to check xmlns individually. */ 1011
655 continue; 1012 /* TODO: Handle tracking jb(r) here? */
656 } else if(!strcmp(y->name, "delay") && !strcmp(xmlns, NS_DELAYED_DELIVERY)) { 1013
657 /* XXX: compare the time. jabber:x:delay can happen on presence packets that aren't really and truly delayed */ 1014 if (presence.chat)
658 delayed = TRUE; 1015 ret = handle_presence_chat(js, &presence, packet);
659 stamp = xmlnode_get_attrib(y, "stamp"); 1016 else
660 } else if(!strcmp(y->name, "c") && !strcmp(xmlns, "http://jabber.org/protocol/caps")) { 1017 ret = handle_presence_contact(js, &presence);
661 caps = y; /* store for later, when creating buddy resource */ 1018 if (!ret)
662 } else if (g_str_equal(y->name, "nick") && g_str_equal(xmlns, "http://jabber.org/protocol/nick")) { 1019 goto out;
663 nickname = xmlnode_get_data(y); 1020
664 } else if(!strcmp(y->name, "x")) { 1021 if (presence.caps && presence.type == JABBER_PRESENCE_AVAILABLE) {
665 if(!strcmp(xmlns, NS_DELAYED_DELIVERY_LEGACY)) {
666 /* XXX: compare the time. jabber:x:delay can happen on presence packets that aren't really and truly delayed */
667 delayed = TRUE;
668 stamp = xmlnode_get_attrib(y, "stamp");
669 } else if(!strcmp(xmlns, "http://jabber.org/protocol/muc#user")) {
670 } else if(!strcmp(xmlns, "vcard-temp:x:update")) {
671 xmlnode *photo = xmlnode_get_child(y, "photo");
672 if(photo) {
673 g_free(avatar_hash);
674 avatar_hash = xmlnode_get_data(photo);
675 }
676 }
677 } else if (!strcmp(y->name, "query") &&
678 !strcmp(xmlnode_get_namespace(y), NS_LAST_ACTIVITY)) {
679 /* resource has specified idle */
680 const gchar *seconds = xmlnode_get_attrib(y, "seconds");
681 if (seconds) {
682 /* we may need to take "delayed" into account here */
683 idle = atoi(seconds);
684 }
685 }
686 }
687
688 if (idle && delayed && stamp) {
689 /* if we have a delayed presence, we need to add the delay to the idle
690 value */
691 time_t offset = time(NULL) - purple_str_to_time(stamp, TRUE, NULL, NULL,
692 NULL);
693 purple_debug_info("jabber", "got delay %s yielding %ld s offset\n",
694 stamp, offset);
695 idle += offset;
696 }
697
698 /* DEALING WITH CHATS */
699 if(jid->node && (chat = jabber_chat_find(js, jid->node, jid->domain))) {
700 static int i = 1;
701
702 if(state == JABBER_BUDDY_STATE_ERROR) {
703 char *title, *msg = jabber_parse_error(js, packet, NULL);
704
705 if (!chat->conv) {
706 title = g_strdup_printf(_("Error joining chat %s"), from);
707 purple_serv_got_join_chat_failed(js->gc, chat->components);
708 } else {
709 title = g_strdup_printf(_("Error in chat %s"), from);
710 if (g_hash_table_size(chat->members) == 0)
711 serv_got_chat_left(js->gc, chat->id);
712 }
713 purple_notify_error(js->gc, title, title, msg);
714 g_free(title);
715 g_free(msg);
716
717 if (g_hash_table_size(chat->members) == 0)
718 /* Only destroy the chat if the error happened while joining */
719 jabber_chat_destroy(chat);
720 jabber_id_free(jid);
721 g_free(status);
722 g_free(avatar_hash);
723 g_free(nickname);
724 return;
725 }
726
727 if (type == NULL) {
728 xmlnode *x;
729 const char *real_jid = NULL;
730 const char *affiliation = NULL;
731 const char *role = NULL;
732 gboolean is_our_resource = FALSE; /* Is the presence about us? */
733
734 /*
735 * XEP-0045 mandates the presence to include a resource (which is
736 * treated as the chat nick). Some non-compliant servers allow
737 * joining without a nick.
738 */
739 if (!jid->resource) {
740 jabber_id_free(jid);
741 g_free(avatar_hash);
742 g_free(nickname);
743 g_free(status);
744 return;
745 }
746
747 x = xmlnode_get_child_with_namespace(packet, "x",
748 "http://jabber.org/protocol/muc#user");
749 if (x) {
750 xmlnode *status_node;
751 xmlnode *item_node;
752
753 for (status_node = xmlnode_get_child(x, "status"); status_node;
754 status_node = xmlnode_get_next_twin(status_node)) {
755 const char *code = xmlnode_get_attrib(status_node, "code");
756 if (!code)
757 continue;
758
759 if (g_str_equal(code, "110")) {
760 is_our_resource = TRUE;
761 } else if (g_str_equal(code, "201")) {
762 if ((chat = jabber_chat_find(js, jid->node, jid->domain))) {
763 chat->config_dialog_type = PURPLE_REQUEST_ACTION;
764 chat->config_dialog_handle =
765 purple_request_action(js->gc,
766 _("Create New Room"),
767 _("Create New Room"),
768 _("You are creating a new room. Would"
769 " you like to configure it, or"
770 " accept the default settings?"),
771 /* Default Action */ 1,
772 account, NULL, chat->conv,
773 chat, 2,
774 _("_Configure Room"), G_CALLBACK(jabber_chat_request_room_configure),
775 _("_Accept Defaults"), G_CALLBACK(jabber_chat_create_instant_room));
776 }
777 } else if (g_str_equal(code, "210")) {
778 /* server rewrote room-nick */
779 if((chat = jabber_chat_find(js, jid->node, jid->domain))) {
780 g_free(chat->handle);
781 chat->handle = g_strdup(jid->resource);
782 }
783 }
784 }
785
786 item_node = xmlnode_get_child(x, "item");
787 if (item_node) {
788 real_jid = xmlnode_get_attrib(item_node, "jid");
789 affiliation = xmlnode_get_attrib(item_node, "affiliation");
790 role = xmlnode_get_attrib(item_node, "role");
791
792 if (purple_strequal(affiliation, "owner"))
793 flags |= PURPLE_CBFLAGS_FOUNDER;
794 if (role) {
795 if (g_str_equal(role, "moderator"))
796 flags |= PURPLE_CBFLAGS_OP;
797 else if (g_str_equal(role, "participant"))
798 flags |= PURPLE_CBFLAGS_VOICE;
799 }
800 }
801 }
802
803 if(!chat->conv) {
804 char *room_jid = g_strdup_printf("%s@%s", jid->node, jid->domain);
805 chat->id = i++;
806 chat->muc = (x != NULL);
807 chat->conv = serv_got_joined_chat(js->gc, chat->id, room_jid);
808 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(chat->conv), chat->handle);
809
810 jabber_chat_disco_traffic(chat);
811 g_free(room_jid);
812 }
813
814 jbr = jabber_buddy_track_resource(jb, jid->resource, priority, state,
815 status);
816 jbr->commands_fetched = TRUE;
817
818 jabber_chat_track_handle(chat, jid->resource, real_jid, affiliation, role);
819
820 if(!jabber_chat_find_buddy(chat->conv, jid->resource))
821 purple_conv_chat_add_user(PURPLE_CONV_CHAT(chat->conv), jid->resource,
822 real_jid, flags, !delayed);
823 else
824 purple_conv_chat_user_set_flags(PURPLE_CONV_CHAT(chat->conv), jid->resource,
825 flags);
826 } else if (g_str_equal(type, "unavailable")) {
827 xmlnode *x;
828 gboolean nick_change = FALSE;
829 gboolean kick = FALSE;
830 gboolean is_our_resource = FALSE; /* Is the presence about us? */
831
832 /* If the chat nick is invalid, we haven't yet joined, or we've
833 * already left (it was probably us leaving after we closed the
834 * chat), we don't care.
835 */
836 if (!jid->resource || !chat->conv || chat->left) {
837 if (chat->left &&
838 jid->resource && chat->handle && !strcmp(jid->resource, chat->handle))
839 jabber_chat_destroy(chat);
840 jabber_id_free(jid);
841 g_free(status);
842 g_free(avatar_hash);
843 g_free(nickname);
844 return;
845 }
846
847 is_our_resource = (0 == g_utf8_collate(jid->resource, chat->handle));
848
849 jabber_buddy_remove_resource(jb, jid->resource);
850
851 x = xmlnode_get_child_with_namespace(packet, "x",
852 "http://jabber.org/protocol/muc#user");
853 if (chat->muc && x) {
854 const char *nick;
855 const char *item_jid = NULL;
856 const char *to;
857 xmlnode *stat;
858 xmlnode *item;
859
860 item = xmlnode_get_child(x, "item");
861 if (item)
862 item_jid = xmlnode_get_attrib(item, "jid");
863
864 for (stat = xmlnode_get_child(x, "status"); stat;
865 stat = xmlnode_get_next_twin(stat)) {
866 const char *code = xmlnode_get_attrib(stat, "code");
867
868 if (!code)
869 continue;
870
871 if (g_str_equal(code, "110")) {
872 is_our_resource = TRUE;
873 } else if(!strcmp(code, "301")) {
874 /* XXX: we got banned */
875 } else if(!strcmp(code, "303") && item &&
876 (nick = xmlnode_get_attrib(item, "nick"))) {
877 nick_change = TRUE;
878 if(!strcmp(jid->resource, chat->handle)) {
879 g_free(chat->handle);
880 chat->handle = g_strdup(nick);
881 }
882
883 /* TODO: This should probably be moved out of the loop */
884 purple_conv_chat_rename_user(PURPLE_CONV_CHAT(chat->conv), jid->resource, nick);
885 jabber_chat_remove_handle(chat, jid->resource);
886 continue;
887 } else if(!strcmp(code, "307")) {
888 /* Someone was kicked from the room */
889 xmlnode *reason = NULL, *actor = NULL;
890 const char *actor_name = NULL;
891 char *reason_text = NULL;
892 char *tmp;
893
894 kick = TRUE;
895
896 if (item) {
897 reason = xmlnode_get_child(item, "reason");
898 actor = xmlnode_get_child(item, "actor");
899
900 if (reason != NULL)
901 reason_text = xmlnode_get_data(reason);
902 if (actor != NULL)
903 actor_name = xmlnode_get_attrib(actor, "jid");
904 }
905
906 if (reason_text == NULL)
907 reason_text = g_strdup(_("No reason"));
908
909 if (is_our_resource) {
910 if (actor_name != NULL)
911 tmp = g_strdup_printf(_("You have been kicked by %s: (%s)"),
912 actor_name, reason_text);
913 else
914 tmp = g_strdup_printf(_("You have been kicked: (%s)"),
915 reason_text);
916 } else {
917 if (actor_name != NULL)
918 tmp = g_strdup_printf(_("Kicked by %s (%s)"),
919 actor_name, reason_text);
920 else
921 tmp = g_strdup_printf(_("Kicked (%s)"),
922 reason_text);
923 }
924
925 g_free(reason_text);
926 g_free(status);
927 status = tmp;
928 } else if(!strcmp(code, "321")) {
929 /* XXX: removed due to an affiliation change */
930 } else if(!strcmp(code, "322")) {
931 /* XXX: removed because room is now members-only */
932 } else if(!strcmp(code, "332")) {
933 /* XXX: removed due to system shutdown */
934 }
935 }
936
937 /*
938 * Possibly another connected resource of our JID (see XEP-0045
939 * v1.24 section 7.1.10) being disconnected. Should be
940 * distinguished by the item_jid.
941 * Also possibly works around bits of an Openfire bug. See
942 * #8319.
943 */
944 to = xmlnode_get_attrib(packet, "to");
945 if (is_our_resource && item_jid && !purple_strequal(to, item_jid)) {
946 /* TODO: When the above is a loop, this needs to still act
947 * sanely for all cases (this code is a little fragile). */
948 if (!kick && !nick_change)
949 /* Presumably, kicks and nick changes also affect us. */
950 is_our_resource = FALSE;
951 }
952 }
953 if(!nick_change) {
954 if (is_our_resource) {
955 if (kick)
956 purple_conv_chat_write(PURPLE_CONV_CHAT(chat->conv), jid->resource,
957 status, PURPLE_MESSAGE_SYSTEM, time(NULL));
958
959 serv_got_chat_left(js->gc, chat->id);
960 jabber_chat_destroy(chat);
961 } else {
962 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(chat->conv), jid->resource,
963 status);
964 jabber_chat_remove_handle(chat, jid->resource);
965 }
966 }
967 } else {
968 /* A type that isn't available or unavailable */
969 purple_debug_error("jabber", "MUC presence with bad type: %s\n",
970 type);
971
972 jabber_id_free(jid);
973 g_free(avatar_hash);
974 g_free(status);
975 g_free(nickname);
976 g_return_if_reached();
977 }
978 /* End of DEALING WITH CHATS...about 5000 lines ago */
979 } else {
980 /* DEALING WITH CONTACT (i.e. not a chat) */
981 PurpleConversation *conv;
982
983 buddy_name = g_strdup_printf("%s%s%s", jid->node ? jid->node : "",
984 jid->node ? "@" : "", jid->domain);
985
986 /*
987 * Unbind/unlock from sending messages to a specific resource on
988 * presence changes. This is locked to a specific resource when
989 * receiving a message (in message.c).
990 */
991 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
992 buddy_name, account);
993 if (conv) {
994 purple_debug_info("jabber", "Changed conversation binding from %s to %s\n",
995 purple_conversation_get_name(conv), buddy_name);
996 purple_conversation_set_name(conv, buddy_name);
997 }
998
999 if((b = purple_find_buddy(account, buddy_name)) == NULL) {
1000 if (jb != js->user_jb) {
1001 purple_debug_warning("jabber", "Got presence for unknown buddy %s on account %s (%p)\n",
1002 buddy_name, purple_account_get_username(account), account);
1003 jabber_id_free(jid);
1004 g_free(avatar_hash);
1005 g_free(buddy_name);
1006 g_free(nickname);
1007 g_free(status);
1008 return;
1009 } else {
1010 /* this is a different resource of our own account. Resume even when this account isn't on our blist */
1011 }
1012 }
1013
1014 if(b && avatar_hash) {
1015 const char *avatar_hash2 = purple_buddy_icons_get_checksum_for_user(b);
1016 if(!avatar_hash2 || strcmp(avatar_hash, avatar_hash2)) {
1017 JabberIq *iq;
1018 xmlnode *vcard;
1019
1020 /* XXX this is a crappy way of trying to prevent
1021 * someone from spamming us with presence packets
1022 * and causing us to DoS ourselves...what we really
1023 * need is a queue system that can throttle itself,
1024 * but i'm too tired to write that right now */
1025 if(!g_slist_find(js->pending_avatar_requests, jb)) {
1026
1027 js->pending_avatar_requests = g_slist_prepend(js->pending_avatar_requests, jb);
1028
1029 iq = jabber_iq_new(js, JABBER_IQ_GET);
1030 xmlnode_set_attrib(iq->node, "to", buddy_name);
1031 vcard = xmlnode_new_child(iq->node, "vCard");
1032 xmlnode_set_namespace(vcard, "vcard-temp");
1033
1034 jabber_iq_set_callback(iq, jabber_vcard_parse_avatar, NULL);
1035 jabber_iq_send(iq);
1036 }
1037 }
1038 }
1039
1040 if(state == JABBER_BUDDY_STATE_ERROR ||
1041 (type && (g_str_equal(type, "unavailable") ||
1042 g_str_equal(type, "unsubscribed")))) {
1043 jabber_buddy_remove_resource(jb, jid->resource);
1044 } else {
1045 jbr = jabber_buddy_track_resource(jb, jid->resource, priority,
1046 state, status);
1047 if (idle) {
1048 jbr->idle = time(NULL) - idle;
1049 } else {
1050 jbr->idle = 0;
1051 }
1052 }
1053
1054 if((found_jbr = jabber_buddy_find_resource(jb, NULL))) {
1055 jabber_google_presence_incoming(js, buddy_name, found_jbr);
1056 purple_prpl_got_user_status(account, buddy_name, jabber_buddy_state_get_status_id(found_jbr->state), "priority", found_jbr->priority, "message", found_jbr->status, NULL);
1057 purple_prpl_got_user_idle(account, buddy_name, found_jbr->idle, found_jbr->idle);
1058 if (nickname)
1059 serv_got_alias(js->gc, buddy_name, nickname);
1060 } else {
1061 purple_prpl_got_user_status(account, buddy_name, "offline", status ? "message" : NULL, status, NULL);
1062 }
1063 g_free(buddy_name);
1064 }
1065
1066 if (caps && !type) {
1067 /* handle Entity Capabilities (XEP-0115) */ 1022 /* handle Entity Capabilities (XEP-0115) */
1068 const char *node = xmlnode_get_attrib(caps, "node"); 1023 const char *node = xmlnode_get_attrib(presence.caps, "node");
1069 const char *ver = xmlnode_get_attrib(caps, "ver"); 1024 const char *ver = xmlnode_get_attrib(presence.caps, "ver");
1070 const char *hash = xmlnode_get_attrib(caps, "hash"); 1025 const char *hash = xmlnode_get_attrib(presence.caps, "hash");
1071 const char *ext = xmlnode_get_attrib(caps, "ext"); 1026 const char *ext = xmlnode_get_attrib(presence.caps, "ext");
1072 1027
1073 /* v1.3 uses: node, ver, and optionally ext. 1028 /* v1.3 uses: node, ver, and optionally ext.
1074 * v1.5 uses: node, ver, and hash. */ 1029 * v1.5 uses: node, ver, and hash. */
1075 if (node && *node && ver && *ver) { 1030 if (node && *node && ver && *ver) {
1076 gchar **exts = ext && *ext ? g_strsplit(ext, " ", -1) : NULL; 1031 gchar **exts = ext && *ext ? g_strsplit(ext, " ", -1) : NULL;
1077 jbr = jabber_buddy_find_resource(jb, jid->resource); 1032 jbr = jabber_buddy_find_resource(presence.jb, presence.jid_from->resource);
1078 1033
1079 /* Look it up if we don't already have all this information */ 1034 /* Look it up if we don't already have all this information */
1080 if (!jbr || !jbr->caps.info || 1035 if (!jbr || !jbr->caps.info ||
1081 !g_str_equal(node, jbr->caps.info->tuple.node) || 1036 !g_str_equal(node, jbr->caps.info->tuple.node) ||
1082 !g_str_equal(ver, jbr->caps.info->tuple.ver) || 1037 !g_str_equal(ver, jbr->caps.info->tuple.ver) ||
1083 !purple_strequal(hash, jbr->caps.info->tuple.hash) || 1038 !purple_strequal(hash, jbr->caps.info->tuple.hash) ||
1084 !jabber_caps_exts_known(jbr->caps.info, (gchar **)exts)) { 1039 !jabber_caps_exts_known(jbr->caps.info, (gchar **)exts)) {
1085 JabberPresenceCapabilities *userdata = g_new0(JabberPresenceCapabilities, 1); 1040 JabberPresenceCapabilities *userdata = g_new0(JabberPresenceCapabilities, 1);
1086 userdata->js = js; 1041 userdata->js = js;
1087 userdata->jb = jb; 1042 userdata->jb = presence.jb;
1088 userdata->from = g_strdup(from); 1043 userdata->from = g_strdup(presence.from);
1089 jabber_caps_get_info(js, from, node, ver, hash, exts, 1044 jabber_caps_get_info(js, presence.from, node, ver, hash, exts,
1090 (jabber_caps_get_info_cb)jabber_presence_set_capabilities, 1045 (jabber_caps_get_info_cb)jabber_presence_set_capabilities,
1091 userdata); 1046 userdata);
1092 } else { 1047 } else {
1093 if (exts) 1048 if (exts)
1094 g_strfreev(exts); 1049 g_strfreev(exts);
1095 } 1050 }
1096 } 1051 }
1097 } 1052 }
1098 1053
1099 g_free(nickname); 1054 out:
1100 g_free(status); 1055 g_free(presence.nickname);
1101 jabber_id_free(jid); 1056 g_free(presence.status);
1102 g_free(avatar_hash); 1057 jabber_id_free(presence.jid_from);
1058 g_free(presence.nickname);
1059 g_free(presence.vcard_avatar_hash);
1103 } 1060 }
1104 1061
1105 void jabber_presence_subscription_set(JabberStream *js, const char *who, const char *type) 1062 void jabber_presence_subscription_set(JabberStream *js, const char *who, const char *type)
1106 { 1063 {
1107 xmlnode *presence = xmlnode_new("presence"); 1064 xmlnode *presence = xmlnode_new("presence");
1140 1097
1141 if(priority) 1098 if(priority)
1142 *priority = purple_status_get_attr_int(status, "priority"); 1099 *priority = purple_status_get_attr_int(status, "priority");
1143 } 1100 }
1144 } 1101 }
1102
1103 /* Incoming presence handlers */
1104 static void
1105 parse_priority(JabberStream *js, JabberPresence *presence, xmlnode *priority)
1106 {
1107 char *p = xmlnode_get_data(priority);
1108
1109 if (presence->priority != 0)
1110 purple_debug_warning("jabber", "presence stanza received with multiple "
1111 "priority children!?\n");
1112
1113 if (p) {
1114 presence->priority = atoi(p);
1115 g_free(p);
1116 } else
1117 purple_debug_warning("jabber", "Empty <priority/> in presence!\n");
1118 }
1119
1120 static void
1121 parse_show(JabberStream *js, JabberPresence *presence, xmlnode *show)
1122 {
1123 char *cdata;
1124
1125 if (presence->type != JABBER_PRESENCE_AVAILABLE) {
1126 purple_debug_warning("jabber", "<show/> present on presence, but "
1127 "type is not default ('available')\n");
1128 return;
1129 }
1130
1131 cdata = xmlnode_get_data(show);
1132 if (cdata) {
1133 presence->state = jabber_buddy_show_get_state(cdata);
1134 g_free(cdata);
1135 } else
1136 purple_debug_warning("jabber", "<show/> present on presence, but "
1137 "no contents!\n");
1138 }
1139
1140 static void
1141 parse_status(JabberStream *js, JabberPresence *presence, xmlnode *status)
1142 {
1143 /* TODO: Check/track language attribute? */
1144
1145 g_free(presence->status);
1146 presence->status = xmlnode_get_data(status);
1147 }
1148
1149 static void
1150 parse_delay(JabberStream *js, JabberPresence *presence, xmlnode *delay)
1151 {
1152 /* XXX: compare the time. Can happen on presence stanzas that aren't
1153 * actually delayed.
1154 */
1155 const char *stamp = xmlnode_get_attrib(delay, "stamp");
1156 presence->delayed = TRUE;
1157 presence->sent = purple_str_to_time(stamp, TRUE, NULL, NULL, NULL);
1158 }
1159
1160 static void
1161 parse_idle(JabberStream *js, JabberPresence *presence, xmlnode *query)
1162 {
1163 const gchar *seconds = xmlnode_get_attrib(query, "seconds");
1164 if (seconds) {
1165 presence->idle = atoi(seconds);
1166 if (presence->idle < 0) {
1167 purple_debug_warning("jabber", "Received bogus idle time %s\n", seconds);
1168 presence->idle = 0;
1169 }
1170 }
1171 }
1172
1173 static void
1174 parse_caps(JabberStream *js, JabberPresence *presence, xmlnode *c)
1175 {
1176 /* TODO: Move the rest of the caps handling in here, after changing the
1177 * the "do we have details about this (node, ver) and exts" to not
1178 * require the jbr to be present (since that happens later).
1179 */
1180 presence->caps = c;
1181 }
1182
1183 static void
1184 parse_nickname(JabberStream *js, JabberPresence *presence, xmlnode *nick)
1185 {
1186 g_free(presence->nickname);
1187 presence->nickname = xmlnode_get_data(nick);
1188 }
1189
1190 static void
1191 parse_vcard_avatar(JabberStream *js, JabberPresence *presence, xmlnode *x)
1192 {
1193 xmlnode *photo = xmlnode_get_child(x, "photo");
1194 if (photo) {
1195 g_free(presence->vcard_avatar_hash);
1196 presence->vcard_avatar_hash = xmlnode_get_data(photo);
1197 }
1198 }
1199
1200 static void
1201 parse_muc_user(JabberStream *js, JabberPresence *presence, xmlnode *x)
1202 {
1203 xmlnode *status;
1204
1205 if (presence->chat == NULL) {
1206 purple_debug_warning("jabber", "Ignoring MUC gloop on non-MUC presence\n");
1207 return;
1208 }
1209
1210 if (presence->chat->conv == NULL)
1211 presence->chat->muc = TRUE;
1212
1213 for (status = xmlnode_get_child(x, "status"); status;
1214 status = xmlnode_get_next_twin(status)) {
1215 const char *code = xmlnode_get_attrib(status, "code");
1216 int val;
1217 if (!code)
1218 continue;
1219
1220 val = atoi(code);
1221 if (val == 0 || val < 0) {
1222 purple_debug_warning("jabber", "Ignoring bogus status code '%s'\n",
1223 code);
1224 continue;
1225 }
1226
1227 presence->chat_info.codes = g_slist_prepend(presence->chat_info.codes, GINT_TO_POINTER(val));
1228 }
1229
1230 presence->chat_info.item = xmlnode_get_child(x, "item");
1231 }
1232
1233 void jabber_presence_register_handler(const char *node, const char *xmlns,
1234 JabberPresenceHandler *handler)
1235 {
1236 /*
1237 * This is valid because nodes nor namespaces cannot have spaces in them
1238 * (see http://www.w3.org/TR/2006/REC-xml-20060816/ and
1239 * http://www.w3.org/TR/REC-xml-names/)
1240 */
1241 char *key = g_strdup_printf("%s %s", node, xmlns);
1242 g_hash_table_replace(presence_handlers, key, handler);
1243 }
1244
1245 void jabber_presence_init(void)
1246 {
1247 presence_handlers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1248
1249 /* Core RFC things */
1250 jabber_presence_register_handler("priority", "jabber:client", parse_priority);
1251 jabber_presence_register_handler("show", "jabber:client", parse_show);
1252 jabber_presence_register_handler("status", "jabber:client", parse_status);
1253
1254 /* XEPs */
1255 jabber_presence_register_handler("c", "http://jabber.org/protocol/caps", parse_caps);
1256 jabber_presence_register_handler("delay", NS_DELAYED_DELIVERY, parse_delay);
1257 jabber_presence_register_handler("nick", "http://jabber.org/protocol/nick", parse_nickname);
1258 jabber_presence_register_handler("query", NS_LAST_ACTIVITY, parse_idle);
1259 jabber_presence_register_handler("x", NS_DELAYED_DELIVERY_LEGACY, parse_delay);
1260 jabber_presence_register_handler("x", "http://jabber.org/protocol/muc#user", parse_muc_user);
1261 jabber_presence_register_handler("x", "vcard-temp:x:update", parse_vcard_avatar);
1262 }
1263
1264 void jabber_presence_uninit(void)
1265 {
1266 g_hash_table_destroy(presence_handlers);
1267 presence_handlers = NULL;
1268 }