2 -- Copyright (C) 2008-2010 Matthew Wild
3 -- Copyright (C) 2008-2010 Waqas Hussain
5 -- This project is MIT/X11 licensed. Please see the
6 -- COPYING file in the source package for more information.
12 local log = require "util.logger".init("rostermanager");
15 local tostring = tostring;
19 local bare_sessions = prosody.bare_sessions;
21 local datamanager = require "util.datamanager"
22 local um_user_exists = require "core.usermanager".user_exists;
23 local st = require "util.stanza";
27 local save_roster; -- forward declaration
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
36 session.roster[jid] = old_item;
37 return nil, "wait", "internal-server-error", "Unable to save roster";
40 return nil, "auth", "not-authorized", "Session's roster not loaded";
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
51 session.roster[jid] = old_item;
52 return nil, "wait", "internal-server-error", "Unable to save roster";
55 return nil, "auth", "not-authorized", "Session's roster not loaded";
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;
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") });
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();
71 stanza:tag("item", {jid = jid, subscription = "remove"});
73 stanza:up(); -- move out from item
74 stanza:up(); -- move out from stanza
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?
85 local function roster_metadata(roster, err)
86 local metadata = roster[false];
88 metadata = { broken = err or nil };
89 roster[false] = metadata;
91 if roster.pending and type(roster.pending.subscription) ~= "string" then
92 metadata.pending = roster.pending;
94 elseif not metadata.pending then
95 metadata.pending = {};
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];
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);
112 local data, err = datamanager.load(username, host, "roster");
114 if user then user.roster = roster; end
115 roster_metadata(roster, err);
118 log("warn", "roster for %s has a self-contact", jid);
121 hosts[host].events.fire_event("roster-load", { username = username, host = host, roster = roster });
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);
132 log("debug", "save_roster: saving roster for %s@%s", username, host);
134 roster = hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster;
136 -- --roster = load_roster(username, host);
137 -- return true; -- roster unchanged, no reason to save
141 local metadata = roster_metadata(roster);
142 if metadata.version ~= true then
143 metadata.version = (metadata.version or 0) + 1;
145 if metadata.broken then return nil, "Not saving broken roster" end
146 return datamanager.store(username, host, "roster", roster);
148 log("warn", "save_roster: user had no roster to save");
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";
162 return save_roster(username, host, roster);
166 local is_contact_pending_out -- forward declaration
168 local function process_inbound_subscription_cancellation(username, host, jid)
169 local roster = load_roster(username, host);
170 local item = roster[jid];
172 if is_contact_pending_out(username, host, jid) then
177 if item.subscription == "to" then
178 item.subscription = "none";
180 elseif item.subscription == "both" then
181 item.subscription = "from";
186 return save_roster(username, host, roster);
190 local is_contact_pending_in -- forward declaration
192 local function process_inbound_unsubscribe(username, host, jid)
193 local roster = load_roster(username, host);
194 local item = roster[jid];
196 if is_contact_pending_in(username, host, jid) then
197 roster[false].pending[jid] = nil;
201 if item.subscription == "from" then
202 item.subscription = "none";
204 elseif item.subscription == "both" then
205 item.subscription = "to";
210 return save_roster(username, host, roster);
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;
219 local function is_contact_subscribed(username, host, jid)
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
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;
232 function is_contact_pending_in(username, host, jid)
233 local roster = load_roster(username, host);
234 return roster[false].pending[jid];
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
242 roster[false].pending[jid] = true;
243 return save_roster(username, host, roster);
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;
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
257 item = {subscription = "none", groups = {}};
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);
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
272 if item.subscription == "both" then
273 item.subscription = "from";
274 elseif item.subscription == "to" then
275 item.subscription = "none";
277 return save_roster(username, host, roster);
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 = {}};
287 if item.subscription == "none" then
288 item.subscription = "from";
289 else -- subscription == to
290 item.subscription = "both";
292 roster[false].pending[jid] = nil;
293 return save_roster(username, host, roster);
294 end -- TODO else implement optional feature pre-approval (ask = subscribed)
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);
301 roster[false].pending[jid] = nil;
305 if item.subscription == "from" then
306 item.subscription = "none";
308 elseif item.subscription == "both" then
309 item.subscription = "to";
313 local success = (pending or subscribed) and save_roster(username, host, roster);
314 return success, pending, subscribed;
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);
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);
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;