Comparison

plugins/muc/muc.lib.lua @ 6093:9a7eaf0a35b6

plugins/muc/muc.lib: Split up `handle_to_room` into smaller handlers (thanks sysko)
author daurnimator <quae@daurnimator.com>
date Thu, 20 Feb 2014 14:36:49 -0500
parent 6092:16d5049fe842
child 6094:db2faeb151b6
comparison
equal deleted inserted replaced
6092:16d5049fe842 6093:9a7eaf0a35b6
769 end 769 end
770 self:set_persistent(false); 770 self:set_persistent(false);
771 module:fire_event("muc-room-destroyed", { room = self }); 771 module:fire_event("muc-room-destroyed", { room = self });
772 end 772 end
773 773
774 function room_mt:handle_to_room(origin, stanza) -- presence changes and groupchat messages, along with disco/etc 774 function room_mt:handle_iq_to_room(origin, stanza)
775 local type = stanza.attr.type; 775 local type = stanza.attr.type;
776 local xmlns = stanza.tags[1] and stanza.tags[1].attr.xmlns; 776 local xmlns = stanza.tags[1] and stanza.tags[1].attr.xmlns;
777 if stanza.name == "iq" then 777 if xmlns == "http://jabber.org/protocol/disco#info" and type == "get" and not stanza.tags[1].attr.node then
778 if xmlns == "http://jabber.org/protocol/disco#info" and type == "get" and not stanza.tags[1].attr.node then 778 origin.send(self:get_disco_info(stanza));
779 origin.send(self:get_disco_info(stanza)); 779 elseif xmlns == "http://jabber.org/protocol/disco#items" and type == "get" and not stanza.tags[1].attr.node then
780 elseif xmlns == "http://jabber.org/protocol/disco#items" and type == "get" and not stanza.tags[1].attr.node then 780 origin.send(self:get_disco_items(stanza));
781 origin.send(self:get_disco_items(stanza)); 781 elseif xmlns == "http://jabber.org/protocol/muc#admin" then
782 elseif xmlns == "http://jabber.org/protocol/muc#admin" then 782 local actor = stanza.attr.from;
783 local actor = stanza.attr.from; 783 local affiliation = self:get_affiliation(actor);
784 local affiliation = self:get_affiliation(actor); 784 local current_nick = self._jid_nick[actor];
785 local current_nick = self._jid_nick[actor]; 785 local role = current_nick and self._occupants[current_nick].role or self:get_default_role(affiliation);
786 local role = current_nick and self._occupants[current_nick].role or self:get_default_role(affiliation); 786 local item = stanza.tags[1].tags[1];
787 local item = stanza.tags[1].tags[1]; 787 if item and item.name == "item" then
788 if item and item.name == "item" then 788 if type == "set" then
789 if type == "set" then 789 local callback = function() origin.send(st.reply(stanza)); end
790 local callback = function() origin.send(st.reply(stanza)); end 790 if item.attr.jid then -- Validate provided JID
791 if item.attr.jid then -- Validate provided JID 791 item.attr.jid = jid_prep(item.attr.jid);
792 item.attr.jid = jid_prep(item.attr.jid); 792 if not item.attr.jid then
793 if not item.attr.jid then 793 origin.send(st.error_reply(stanza, "modify", "jid-malformed"));
794 origin.send(st.error_reply(stanza, "modify", "jid-malformed")); 794 return;
795 return; 795 end
796 end
797 if not item.attr.jid and item.attr.nick then -- COMPAT Workaround for Miranda sending 'nick' instead of 'jid' when changing affiliation
798 local occupant = self._occupants[self.jid.."/"..item.attr.nick];
799 if occupant then item.attr.jid = occupant.jid; end
800 elseif not item.attr.nick and item.attr.jid then
801 local nick = self._jid_nick[item.attr.jid];
802 if nick then item.attr.nick = select(3, jid_split(nick)); end
803 end
804 local reason = item.tags[1] and item.tags[1].name == "reason" and #item.tags[1] == 1 and item.tags[1][1];
805 if item.attr.affiliation and item.attr.jid and not item.attr.role then
806 local success, errtype, err = self:set_affiliation(actor, item.attr.jid, item.attr.affiliation, callback, reason);
807 if not success then origin.send(st.error_reply(stanza, errtype, err)); end
808 elseif item.attr.role and item.attr.nick and not item.attr.affiliation then
809 local success, errtype, err = self:set_role(actor, self.jid.."/"..item.attr.nick, item.attr.role, callback, reason);
810 if not success then origin.send(st.error_reply(stanza, errtype, err)); end
811 else
812 origin.send(st.error_reply(stanza, "cancel", "bad-request"));
813 end
814 elseif type == "get" then
815 local _aff = item.attr.affiliation;
816 local _rol = item.attr.role;
817 if _aff and not _rol then
818 if affiliation == "owner" or (affiliation == "admin" and _aff ~= "owner" and _aff ~= "admin") then
819 local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin");
820 for jid, affiliation in pairs(self._affiliations) do
821 if affiliation == _aff then
822 reply:tag("item", {affiliation = _aff, jid = jid}):up();
823 end
796 end 824 end
825 origin.send(reply);
826 else
827 origin.send(st.error_reply(stanza, "auth", "forbidden"));
797 end 828 end
798 if not item.attr.jid and item.attr.nick then -- COMPAT Workaround for Miranda sending 'nick' instead of 'jid' when changing affiliation 829 elseif _rol and not _aff then
799 local occupant = self._occupants[self.jid.."/"..item.attr.nick]; 830 if role == "moderator" then
800 if occupant then item.attr.jid = occupant.jid; end 831 -- TODO allow admins and owners not in room? Provide read-only access to everyone who can see the participants anyway?
801 elseif not item.attr.nick and item.attr.jid then 832 if _rol == "none" then _rol = nil; end
802 local nick = self._jid_nick[item.attr.jid]; 833 local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin");
803 if nick then item.attr.nick = select(3, jid_split(nick)); end 834 for occupant_jid, occupant in pairs(self._occupants) do
835 if occupant.role == _rol then
836 reply:tag("item", {
837 nick = select(3, jid_split(occupant_jid)),
838 role = _rol or "none",
839 affiliation = occupant.affiliation or "none",
840 jid = occupant.jid
841 }):up();
842 end
843 end
844 origin.send(reply);
845 else
846 origin.send(st.error_reply(stanza, "auth", "forbidden"));
804 end 847 end
805 local reason = item.tags[1] and item.tags[1].name == "reason" and #item.tags[1] == 1 and item.tags[1][1]; 848 else
806 if item.attr.affiliation and item.attr.jid and not item.attr.role then 849 origin.send(st.error_reply(stanza, "cancel", "bad-request"));
807 local success, errtype, err = self:set_affiliation(actor, item.attr.jid, item.attr.affiliation, callback, reason); 850 end
808 if not success then origin.send(st.error_reply(stanza, errtype, err)); end 851 end
809 elseif item.attr.role and item.attr.nick and not item.attr.affiliation then 852 elseif type == "set" or type == "get" then
810 local success, errtype, err = self:set_role(actor, self.jid.."/"..item.attr.nick, item.attr.role, callback, reason); 853 origin.send(st.error_reply(stanza, "cancel", "bad-request"));
811 if not success then origin.send(st.error_reply(stanza, errtype, err)); end 854 end
812 else 855 elseif xmlns == "http://jabber.org/protocol/muc#owner" and (type == "get" or type == "set") and stanza.tags[1].name == "query" then
813 origin.send(st.error_reply(stanza, "cancel", "bad-request")); 856 if self:get_affiliation(stanza.attr.from) ~= "owner" then
857 origin.send(st.error_reply(stanza, "auth", "forbidden", "Only owners can configure rooms"));
858 elseif stanza.attr.type == "get" then
859 self:send_form(origin, stanza);
860 elseif stanza.attr.type == "set" then
861 local child = stanza.tags[1].tags[1];
862 if not child then
863 origin.send(st.error_reply(stanza, "modify", "bad-request"));
864 elseif child.name == "destroy" then
865 local newjid = child.attr.jid;
866 local reason, password;
867 for _,tag in ipairs(child.tags) do
868 if tag.name == "reason" then
869 reason = #tag.tags == 0 and tag[1];
870 elseif tag.name == "password" then
871 password = #tag.tags == 0 and tag[1];
814 end 872 end
815 elseif type == "get" then 873 end
816 local _aff = item.attr.affiliation; 874 self:destroy(newjid, reason, password);
817 local _rol = item.attr.role; 875 origin.send(st.reply(stanza));
818 if _aff and not _rol then 876 else
819 if affiliation == "owner" or (affiliation == "admin" and _aff ~= "owner" and _aff ~= "admin") then 877 self:process_form(origin, stanza);
820 local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); 878 end
821 for jid, affiliation in pairs(self._affiliations) do 879 end
822 if affiliation == _aff then 880 elseif type == "set" or type == "get" then
823 reply:tag("item", {affiliation = _aff, jid = jid}):up(); 881 origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
824 end 882 end
825 end 883 end
826 origin.send(reply); 884
827 else 885 function room_mt:handle_groupchat_to_room(origin, stanza)
828 origin.send(st.error_reply(stanza, "auth", "forbidden")); 886 local from = stanza.attr.from;
829 end 887 local current_nick = self._jid_nick[from];
830 elseif _rol and not _aff then 888 local occupant = self._occupants[current_nick];
831 if role == "moderator" then 889 if not occupant then -- not in room
832 -- TODO allow admins and owners not in room? Provide read-only access to everyone who can see the participants anyway? 890 origin.send(st.error_reply(stanza, "cancel", "not-acceptable"));
833 if _rol == "none" then _rol = nil; end 891 elseif occupant.role == "visitor" then
834 local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); 892 origin.send(st.error_reply(stanza, "auth", "forbidden"));
835 for occupant_jid, occupant in pairs(self._occupants) do 893 else
836 if occupant.role == _rol then
837 reply:tag("item", {
838 nick = select(3, jid_split(occupant_jid)),
839 role = _rol or "none",
840 affiliation = occupant.affiliation or "none",
841 jid = occupant.jid
842 }):up();
843 end
844 end
845 origin.send(reply);
846 else
847 origin.send(st.error_reply(stanza, "auth", "forbidden"));
848 end
849 else
850 origin.send(st.error_reply(stanza, "cancel", "bad-request"));
851 end
852 end
853 elseif type == "set" or type == "get" then
854 origin.send(st.error_reply(stanza, "cancel", "bad-request"));
855 end
856 elseif xmlns == "http://jabber.org/protocol/muc#owner" and (type == "get" or type == "set") and stanza.tags[1].name == "query" then
857 if self:get_affiliation(stanza.attr.from) ~= "owner" then
858 origin.send(st.error_reply(stanza, "auth", "forbidden", "Only owners can configure rooms"));
859 elseif stanza.attr.type == "get" then
860 self:send_form(origin, stanza);
861 elseif stanza.attr.type == "set" then
862 local child = stanza.tags[1].tags[1];
863 if not child then
864 origin.send(st.error_reply(stanza, "modify", "bad-request"));
865 elseif child.name == "destroy" then
866 local newjid = child.attr.jid;
867 local reason, password;
868 for _,tag in ipairs(child.tags) do
869 if tag.name == "reason" then
870 reason = #tag.tags == 0 and tag[1];
871 elseif tag.name == "password" then
872 password = #tag.tags == 0 and tag[1];
873 end
874 end
875 self:destroy(newjid, reason, password);
876 origin.send(st.reply(stanza));
877 else
878 self:process_form(origin, stanza);
879 end
880 end
881 elseif type == "set" or type == "get" then
882 origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
883 end
884 elseif stanza.name == "message" and type == "groupchat" then
885 local from = stanza.attr.from; 894 local from = stanza.attr.from;
886 local current_nick = self._jid_nick[from]; 895 stanza.attr.from = current_nick;
887 local occupant = self._occupants[current_nick]; 896 local subject = stanza:get_child_text("subject");
888 if not occupant then -- not in room 897 if subject then
889 origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); 898 if occupant.role == "moderator" or
890 elseif occupant.role == "visitor" then 899 ( self._data.changesubject and occupant.role == "participant" ) then -- and participant
891 origin.send(st.error_reply(stanza, "auth", "forbidden")); 900 self:set_subject(current_nick, subject);
901 else
902 stanza.attr.from = from;
903 origin.send(st.error_reply(stanza, "auth", "forbidden"));
904 end
892 else 905 else
893 local from = stanza.attr.from; 906 self:broadcast_message(stanza, self:get_historylength() > 0 and stanza:get_child("body"));
894 stanza.attr.from = current_nick; 907 end
895 local subject = stanza:get_child_text("subject"); 908 stanza.attr.from = from;
896 if subject then 909 end
897 if occupant.role == "moderator" or 910 end
898 ( self._data.changesubject and occupant.role == "participant" ) then -- and participant 911
899 self:set_subject(current_nick, subject); 912
900 else 913 function room_mt:handle_kickable_to_room(origin, stanza)
901 stanza.attr.from = from; 914 local current_nick = self._jid_nick[stanza.attr.from];
902 origin.send(st.error_reply(stanza, "auth", "forbidden")); 915 log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid);
903 end 916 self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable
904 else 917 end
905 self:broadcast_message(stanza, self:get_historylength() > 0 and stanza:get_child("body")); 918
906 end 919 -- hack - some buggy clients send presence updates to the room rather than their nick
907 stanza.attr.from = from; 920 function room_mt:handle_presence_to_room(origin, stanza)
908 end 921 local type = stanza.attr.type;
909 elseif stanza.name == "message" and type == "error" and is_kickable_error(stanza) then 922 local current_nick = self._jid_nick[stanza.attr.from];
910 local current_nick = self._jid_nick[stanza.attr.from]; 923 if current_nick then
911 log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid);
912 self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable
913 elseif stanza.name == "presence" then -- hack - some buggy clients send presence updates to the room rather than their nick
914 local to = stanza.attr.to; 924 local to = stanza.attr.to;
915 local current_nick = self._jid_nick[stanza.attr.from]; 925 stanza.attr.to = current_nick;
916 if current_nick then 926 self:handle_to_occupant(origin, stanza);
917 stanza.attr.to = current_nick; 927 stanza.attr.to = to;
918 self:handle_to_occupant(origin, stanza); 928 elseif type ~= "error" and type ~= "result" then
919 stanza.attr.to = to; 929 origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
920 elseif type ~= "error" and type ~= "result" then 930 end
921 origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); 931 end
922 end 932
923 elseif stanza.name == "message" and not(type == "chat" or type == "error" or type == "groupchat" or type == "headline") and #stanza.tags == 1 933 function room_mt:handle_invite_to_room(origin, stanza, payload)
934 local _from, _to = stanza.attr.from, stanza.attr.to;
935 local _invitee = jid_prep(payload.attr.to);
936 if _invitee then
937 local _reason = payload.tags[1] and payload.tags[1].name == 'reason' and #payload.tags[1].tags == 0 and payload.tags[1][1];
938 local invite = st.message({from = _to, to = _invitee, id = stanza.attr.id})
939 :tag('x', {xmlns='http://jabber.org/protocol/muc#user'})
940 :tag('invite', {from=_from})
941 :tag('reason'):text(_reason or ""):up()
942 :up();
943 if self:get_password() then
944 invite:tag("password"):text(self:get_password()):up();
945 end
946 invite:up()
947 :tag('x', {xmlns="jabber:x:conference", jid=_to}) -- COMPAT: Some older clients expect this
948 :text(_reason or "")
949 :up()
950 :tag('body') -- Add a plain message for clients which don't support invites
951 :text(_from..' invited you to the room '.._to..(_reason and (' ('.._reason..')') or ""))
952 :up();
953 if self:get_members_only() and not self:get_affiliation(_invitee) then
954 log("debug", "%s invited %s into members only room %s, granting membership", _from, _invitee, _to);
955 self:set_affiliation(_from, _invitee, "member", nil, "Invited by " .. self._jid_nick[_from])
956 end
957 self:_route_stanza(invite);
958 else
959 origin.send(st.error_reply(stanza, "cancel", "jid-malformed"));
960 end
961 end
962
963 function room_mt:handle_message_to_room(origin, stanza)
964 local type = stanza.attr.type;
965 if type == "groupchat" then
966 return self:handle_groupchat_to_room(origin, stanza)
967 elseif type == "error" and is_kickable_error(stanza) then
968 return self:handle_kickable_to_room(origin, stanza)
969 elseif not(type == "chat" or type == "error" or type == "groupchat" or type == "headline") and #stanza.tags == 1
924 and self._jid_nick[stanza.attr.from] and stanza.tags[1].name == "x" and stanza.tags[1].attr.xmlns == "http://jabber.org/protocol/muc#user" then 970 and self._jid_nick[stanza.attr.from] and stanza.tags[1].name == "x" and stanza.tags[1].attr.xmlns == "http://jabber.org/protocol/muc#user" then
925 local x = stanza.tags[1]; 971 local x = stanza.tags[1];
926 local payload = (#x.tags == 1 and x.tags[1]); 972 local payload = (#x.tags == 1 and x.tags[1]);
927 if payload and payload.name == "invite" and payload.attr.to then 973 if payload and payload.name == "invite" and payload.attr.to then
928 local _from, _to = stanza.attr.from, stanza.attr.to; 974 return self:handle_invite_to_room(origin, stanza, payload)
929 local _invitee = jid_prep(payload.attr.to);
930 if _invitee then
931 local _reason = payload.tags[1] and payload.tags[1].name == 'reason' and #payload.tags[1].tags == 0 and payload.tags[1][1];
932 local invite = st.message({from = _to, to = _invitee, id = stanza.attr.id})
933 :tag('x', {xmlns='http://jabber.org/protocol/muc#user'})
934 :tag('invite', {from=_from})
935 :tag('reason'):text(_reason or ""):up()
936 :up();
937 if self:get_password() then
938 invite:tag("password"):text(self:get_password()):up();
939 end
940 invite:up()
941 :tag('x', {xmlns="jabber:x:conference", jid=_to}) -- COMPAT: Some older clients expect this
942 :text(_reason or "")
943 :up()
944 :tag('body') -- Add a plain message for clients which don't support invites
945 :text(_from..' invited you to the room '.._to..(_reason and (' ('.._reason..')') or ""))
946 :up();
947 if self:get_members_only() and not self:get_affiliation(_invitee) then
948 log("debug", "%s invited %s into members only room %s, granting membership", _from, _invitee, _to);
949 self:set_affiliation(_from, _invitee, "member", nil, "Invited by " .. self._jid_nick[_from])
950 end
951 self:_route_stanza(invite);
952 else
953 origin.send(st.error_reply(stanza, "cancel", "jid-malformed"));
954 end
955 else 975 else
956 origin.send(st.error_reply(stanza, "cancel", "bad-request")); 976 origin.send(st.error_reply(stanza, "cancel", "bad-request"));
957 end 977 end
958 else 978 else
959 if type == "error" or type == "result" then return; end 979 if type == "error" or type == "result" then return; end
960 origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); 980 origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
981 end
982 end
983
984 function room_mt:handle_to_room(origin, stanza) -- presence changes and groupchat messages, along with disco/etc
985 if stanza.name == "iq" then
986 return self:handle_iq_to_room(origin, stanza)
987 elseif stanza.name == "message" then
988 return self:handle_message_to_room(origin, stanza)
989 elseif stanza.name == "presence" then
990 return self:handle_presence_to_room(origin, stanza)
961 end 991 end
962 end 992 end
963 993
964 function room_mt:handle_stanza(origin, stanza) 994 function room_mt:handle_stanza(origin, stanza)
965 local to_node, to_host, to_resource = jid_split(stanza.attr.to); 995 local to_node, to_host, to_resource = jid_split(stanza.attr.to);