Mercurial > pidgin
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 } |