fe2eea71e9cc570051cff6bd6bef137a85ac27ee
[prosody.git] / plugins / mod_roster.lua
1 -- Prosody IM
2 -- Copyright (C) 2008-2010 Matthew Wild
3 -- Copyright (C) 2008-2010 Waqas Hussain
4 -- 
5 -- This project is MIT/X11 licensed. Please see the
6 -- COPYING file in the source package for more information.
7 --
8
9
10 local st = require "util.stanza"
11
12 local jid_split = require "util.jid".split;
13 local jid_prep = require "util.jid".prep;
14 local t_concat = table.concat;
15 local tonumber = tonumber;
16 local pairs, ipairs = pairs, ipairs;
17
18 local rm_remove_from_roster = require "core.rostermanager".remove_from_roster;
19 local rm_add_to_roster = require "core.rostermanager".add_to_roster;
20 local rm_roster_push = require "core.rostermanager".roster_push;
21 local core_post_stanza = core_post_stanza;
22
23 module:add_feature("jabber:iq:roster");
24
25 local rosterver_stream_feature = st.stanza("ver", {xmlns="urn:xmpp:features:rosterver"}):tag("optional"):up();
26 module:hook("stream-features", function(event)
27         local origin, features = event.origin, event.features;
28         if origin.username then
29                 features:add_child(rosterver_stream_feature);
30         end
31 end);
32
33 module:hook("iq/self/jabber:iq:roster:query", function(event)
34         local session, stanza = event.origin, event.stanza;
35
36         if stanza.attr.type == "get" then
37                 local roster = st.reply(stanza);
38                 
39                 local client_ver = tonumber(stanza.tags[1].attr.ver);
40                 local server_ver = tonumber(session.roster[false].version or 1);
41                 
42                 if not (client_ver and server_ver) or client_ver ~= server_ver then
43                         roster:query("jabber:iq:roster");
44                         -- Client does not support versioning, or has stale roster
45                         for jid, item in pairs(session.roster) do
46                                 if jid ~= "pending" and jid then
47                                         roster:tag("item", {
48                                                 jid = jid,
49                                                 subscription = item.subscription,
50                                                 ask = item.ask,
51                                                 name = item.name,
52                                         });
53                                         for group in pairs(item.groups) do
54                                                 roster:tag("group"):text(group):up();
55                                         end
56                                         roster:up(); -- move out from item
57                                 end
58                         end
59                         roster.tags[1].attr.ver = server_ver;
60                 end
61                 session.send(roster);
62                 session.interested = true; -- resource is interested in roster updates
63         else -- stanza.attr.type == "set"
64                 local query = stanza.tags[1];
65                 if #query.tags == 1 and query.tags[1].name == "item"
66                                 and query.tags[1].attr.xmlns == "jabber:iq:roster" and query.tags[1].attr.jid
67                                 -- Protection against overwriting roster.pending, until we move it
68                                 and query.tags[1].attr.jid ~= "pending" then
69                         local item = query.tags[1];
70                         local from_node, from_host = jid_split(stanza.attr.from);
71                         local from_bare = from_node and (from_node.."@"..from_host) or from_host; -- bare JID
72                         local jid = jid_prep(item.attr.jid);
73                         local node, host, resource = jid_split(jid);
74                         if not resource and host then
75                                 if jid ~= from_node.."@"..from_host then
76                                         if item.attr.subscription == "remove" then
77                                                 local roster = session.roster;
78                                                 local r_item = roster[jid];
79                                                 if r_item then
80                                                         local to_bare = node and (node.."@"..host) or host; -- bare JID
81                                                         if r_item.subscription == "both" or r_item.subscription == "from" or (roster.pending and roster.pending[jid]) then
82                                                                 core_post_stanza(session, st.presence({type="unsubscribed", from=session.full_jid, to=to_bare}));
83                                                         end
84                                                         if r_item.subscription == "both" or r_item.subscription == "to" or r_item.ask then
85                                                                 core_post_stanza(session, st.presence({type="unsubscribe", from=session.full_jid, to=to_bare}));
86                                                         end
87                                                         local success, err_type, err_cond, err_msg = rm_remove_from_roster(session, jid);
88                                                         if success then
89                                                                 session.send(st.reply(stanza));
90                                                                 rm_roster_push(from_node, from_host, jid);
91                                                         else
92                                                                 session.send(st.error_reply(stanza, err_type, err_cond, err_msg));
93                                                         end
94                                                 else
95                                                         session.send(st.error_reply(stanza, "modify", "item-not-found"));
96                                                 end
97                                         else
98                                                 local r_item = {name = item.attr.name, groups = {}};
99                                                 if r_item.name == "" then r_item.name = nil; end
100                                                 if session.roster[jid] then
101                                                         r_item.subscription = session.roster[jid].subscription;
102                                                         r_item.ask = session.roster[jid].ask;
103                                                 else
104                                                         r_item.subscription = "none";
105                                                 end
106                                                 for _, child in ipairs(item) do
107                                                         if child.name == "group" then
108                                                                 local text = t_concat(child);
109                                                                 if text and text ~= "" then
110                                                                         r_item.groups[text] = true;
111                                                                 end
112                                                         end
113                                                 end
114                                                 local success, err_type, err_cond, err_msg = rm_add_to_roster(session, jid, r_item);
115                                                 if success then
116                                                         -- Ok, send success
117                                                         session.send(st.reply(stanza));
118                                                         -- and push change to all resources
119                                                         rm_roster_push(from_node, from_host, jid);
120                                                 else
121                                                         -- Adding to roster failed
122                                                         session.send(st.error_reply(stanza, err_type, err_cond, err_msg));
123                                                 end
124                                         end
125                                 else
126                                         -- Trying to add self to roster
127                                         session.send(st.error_reply(stanza, "cancel", "not-allowed"));
128                                 end
129                         else
130                                 -- Invalid JID added to roster
131                                 session.send(st.error_reply(stanza, "modify", "bad-request")); -- FIXME what's the correct error?
132                         end
133                 else
134                         -- Roster set didn't include a single item, or its name wasn't  'item'
135                         session.send(st.error_reply(stanza, "modify", "bad-request"));
136                 end
137         end
138         return true;
139 end);