Merge 0.9->0.10
[prosody.git] / core / rostermanager.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
11
12 local log = require "util.logger".init("rostermanager");
13
14 local pairs = pairs;
15 local tostring = tostring;
16 local type = type;
17
18 local hosts = hosts;
19 local bare_sessions = prosody.bare_sessions;
20
21 local datamanager = require "util.datamanager"
22 local um_user_exists = require "core.usermanager".user_exists;
23 local st = require "util.stanza";
24
25 local _ENV = nil;
26
27 local save_roster; -- forward declaration
28
29 local function add_to_roster(session, jid, item)
30         if session.roster then
31                 local old_item = session.roster[jid];
32                 session.roster[jid] = item;
33                 if save_roster(session.username, session.host) then
34                         return true;
35                 else
36                         session.roster[jid] = old_item;
37                         return nil, "wait", "internal-server-error", "Unable to save roster";
38                 end
39         else
40                 return nil, "auth", "not-authorized", "Session's roster not loaded";
41         end
42 end
43
44 local function remove_from_roster(session, jid)
45         if session.roster then
46                 local old_item = session.roster[jid];
47                 session.roster[jid] = nil;
48                 if save_roster(session.username, session.host) then
49                         return true;
50                 else
51                         session.roster[jid] = old_item;
52                         return nil, "wait", "internal-server-error", "Unable to save roster";
53                 end
54         else
55                 return nil, "auth", "not-authorized", "Session's roster not loaded";
56         end
57 end
58
59 local function roster_push(username, host, jid)
60         local roster = jid and hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster;
61         if roster then
62                 local item = hosts[host].sessions[username].roster[jid];
63                 local stanza = st.iq({type="set"});
64                 stanza:tag("query", {xmlns = "jabber:iq:roster", ver = tostring(roster[false].version or "1")  });
65                 if item then
66                         stanza:tag("item", {jid = jid, subscription = item.subscription, name = item.name, ask = item.ask});
67                         for group in pairs(item.groups) do
68                                 stanza:tag("group"):text(group):up();
69                         end
70                 else
71                         stanza:tag("item", {jid = jid, subscription = "remove"});
72                 end
73                 stanza:up(); -- move out from item
74                 stanza:up(); -- move out from stanza
75                 -- stanza ready
76                 for _, session in pairs(hosts[host].sessions[username].sessions) do
77                         if session.interested then
78                                 -- FIXME do we need to set stanza.attr.to?
79                                 session.send(stanza);
80                         end
81                 end
82         end
83 end
84
85 local function roster_metadata(roster, err)
86         local metadata = roster[false];
87         if not metadata then
88                 metadata = { broken = err or nil };
89                 roster[false] = metadata;
90         end
91         if roster.pending and type(roster.pending.subscription) ~= "string" then
92                 metadata.pending = roster.pending;
93                 roster.pending = nil;
94         elseif not metadata.pending then
95                 metadata.pending = {};
96         end
97         return metadata;
98 end
99
100 local function load_roster(username, host)
101         local jid = username.."@"..host;
102         log("debug", "load_roster: asked for: %s", jid);
103         local user = bare_sessions[jid];
104         local roster;
105         if user then
106                 roster = user.roster;
107                 if roster then return roster; end
108                 log("debug", "load_roster: loading for new user: %s@%s", username, host);
109         else -- Attempt to load roster for non-loaded user
110                 log("debug", "load_roster: loading for offline user: %s@%s", username, host);
111         end
112         local data, err = datamanager.load(username, host, "roster");
113         roster = data or {};
114         if user then user.roster = roster; end
115         roster_metadata(roster, err);
116         if roster[jid] then
117                 roster[jid] = nil;
118                 log("warn", "roster for %s has a self-contact", jid);
119         end
120         if not err then
121                 hosts[host].events.fire_event("roster-load", { username = username, host = host, roster = roster });
122         end
123         return roster, err;
124 end
125
126 function save_roster(username, host, roster)
127         if not um_user_exists(username, host) then
128                 log("debug", "not saving roster for %s@%s: the user doesn't exist", username, host);
129                 return nil;
130         end
131
132         log("debug", "save_roster: saving roster for %s@%s", username, host);
133         if not roster then
134                 roster = hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster;
135                 --if not roster then
136                 --      --roster = load_roster(username, host);
137                 --      return true; -- roster unchanged, no reason to save
138                 --end
139         end
140         if roster then
141                 local metadata = roster_metadata(roster);
142                 if metadata.version ~= true then
143                         metadata.version = (metadata.version or 0) + 1;
144                 end
145                 if metadata.broken then return nil, "Not saving broken roster" end
146                 return datamanager.store(username, host, "roster", roster);
147         end
148         log("warn", "save_roster: user had no roster to save");
149         return nil;
150 end
151
152 local function process_inbound_subscription_approval(username, host, jid)
153         local roster = load_roster(username, host);
154         local item = roster[jid];
155         if item and item.ask then
156                 if item.subscription == "none" then
157                         item.subscription = "to";
158                 else -- subscription == from
159                         item.subscription = "both";
160                 end
161                 item.ask = nil;
162                 return save_roster(username, host, roster);
163         end
164 end
165
166 local is_contact_pending_out -- forward declaration
167
168 local function process_inbound_subscription_cancellation(username, host, jid)
169         local roster = load_roster(username, host);
170         local item = roster[jid];
171         local changed = nil;
172         if is_contact_pending_out(username, host, jid) then
173                 item.ask = nil;
174                 changed = true;
175         end
176         if item then
177                 if item.subscription == "to" then
178                         item.subscription = "none";
179                         changed = true;
180                 elseif item.subscription == "both" then
181                         item.subscription = "from";
182                         changed = true;
183                 end
184         end
185         if changed then
186                 return save_roster(username, host, roster);
187         end
188 end
189
190 local is_contact_pending_in -- forward declaration
191
192 local function process_inbound_unsubscribe(username, host, jid)
193         local roster = load_roster(username, host);
194         local item = roster[jid];
195         local changed = nil;
196         if is_contact_pending_in(username, host, jid) then
197                 roster[false].pending[jid] = nil;
198                 changed = true;
199         end
200         if item then
201                 if item.subscription == "from" then
202                         item.subscription = "none";
203                         changed = true;
204                 elseif item.subscription == "both" then
205                         item.subscription = "to";
206                         changed = true;
207                 end
208         end
209         if changed then
210                 return save_roster(username, host, roster);
211         end
212 end
213
214 local function _get_online_roster_subscription(jidA, jidB)
215         local user = bare_sessions[jidA];
216         local item = user and (user.roster[jidB] or { subscription = "none" });
217         return item and item.subscription;
218 end
219 local function is_contact_subscribed(username, host, jid)
220         do
221                 local selfjid = username.."@"..host;
222                 local user_subscription = _get_online_roster_subscription(selfjid, jid);
223                 if user_subscription then return (user_subscription == "both" or user_subscription == "from"); end
224                 local contact_subscription = _get_online_roster_subscription(jid, selfjid);
225                 if contact_subscription then return (contact_subscription == "both" or contact_subscription == "to"); end
226         end
227         local roster, err = load_roster(username, host);
228         local item = roster[jid];
229         return item and (item.subscription == "from" or item.subscription == "both"), err;
230 end
231
232 function is_contact_pending_in(username, host, jid)
233         local roster = load_roster(username, host);
234         return roster[false].pending[jid];
235 end
236 local function set_contact_pending_in(username, host, jid)
237         local roster = load_roster(username, host);
238         local item = roster[jid];
239         if item and (item.subscription == "from" or item.subscription == "both") then
240                 return; -- false
241         end
242         roster[false].pending[jid] = true;
243         return save_roster(username, host, roster);
244 end
245 function is_contact_pending_out(username, host, jid)
246         local roster = load_roster(username, host);
247         local item = roster[jid];
248         return item and item.ask;
249 end
250 local function set_contact_pending_out(username, host, jid) -- subscribe
251         local roster = load_roster(username, host);
252         local item = roster[jid];
253         if item and (item.ask or item.subscription == "to" or item.subscription == "both") then
254                 return true;
255         end
256         if not item then
257                 item = {subscription = "none", groups = {}};
258                 roster[jid] = item;
259         end
260         item.ask = "subscribe";
261         log("debug", "set_contact_pending_out: saving roster; set %s@%s.roster[%q].ask=subscribe", username, host, jid);
262         return save_roster(username, host, roster);
263 end
264 local function unsubscribe(username, host, jid)
265         local roster = load_roster(username, host);
266         local item = roster[jid];
267         if not item then return false; end
268         if (item.subscription == "from" or item.subscription == "none") and not item.ask then
269                 return true;
270         end
271         item.ask = nil;
272         if item.subscription == "both" then
273                 item.subscription = "from";
274         elseif item.subscription == "to" then
275                 item.subscription = "none";
276         end
277         return save_roster(username, host, roster);
278 end
279 local function subscribed(username, host, jid)
280         if is_contact_pending_in(username, host, jid) then
281                 local roster = load_roster(username, host);
282                 local item = roster[jid];
283                 if not item then -- FIXME should roster item be auto-created?
284                         item = {subscription = "none", groups = {}};
285                         roster[jid] = item;
286                 end
287                 if item.subscription == "none" then
288                         item.subscription = "from";
289                 else -- subscription == to
290                         item.subscription = "both";
291                 end
292                 roster[false].pending[jid] = nil;
293                 return save_roster(username, host, roster);
294         end -- TODO else implement optional feature pre-approval (ask = subscribed)
295 end
296 local function unsubscribed(username, host, jid)
297         local roster = load_roster(username, host);
298         local item = roster[jid];
299         local pending = is_contact_pending_in(username, host, jid);
300         if pending then
301                 roster[false].pending[jid] = nil;
302         end
303         local subscribed;
304         if item then
305                 if item.subscription == "from" then
306                         item.subscription = "none";
307                         subscribed = true;
308                 elseif item.subscription == "both" then
309                         item.subscription = "to";
310                         subscribed = true;
311                 end
312         end
313         local success = (pending or subscribed) and save_roster(username, host, roster);
314         return success, pending, subscribed;
315 end
316
317 local function process_outbound_subscription_request(username, host, jid)
318         local roster = load_roster(username, host);
319         local item = roster[jid];
320         if item and (item.subscription == "none" or item.subscription == "from") then
321                 item.ask = "subscribe";
322                 return save_roster(username, host, roster);
323         end
324 end
325
326 --[[function process_outbound_subscription_approval(username, host, jid)
327         local roster = load_roster(username, host);
328         local item = roster[jid];
329         if item and (item.subscription == "none" or item.subscription == "from" then
330                 item.ask = "subscribe";
331                 return save_roster(username, host, roster);
332         end
333 end]]
334
335
336
337 return {
338         add_to_roster = add_to_roster;
339         remove_from_roster = remove_from_roster;
340         roster_push = roster_push;
341         load_roster = load_roster;
342         save_roster = save_roster;
343         process_inbound_subscription_approval = process_inbound_subscription_approval;
344         process_inbound_subscription_cancellation = process_inbound_subscription_cancellation;
345         process_inbound_unsubscribe = process_inbound_unsubscribe;
346         is_contact_subscribed = is_contact_subscribed;
347         is_contact_pending_in = is_contact_pending_in;
348         set_contact_pending_in = set_contact_pending_in;
349         is_contact_pending_out = is_contact_pending_out;
350         set_contact_pending_out = set_contact_pending_out;
351         unsubscribe = unsubscribe;
352         subscribed = subscribed;
353         unsubscribed = unsubscribed;
354         process_outbound_subscription_request = process_outbound_subscription_request;
355 };