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