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 um_user_exists = require "core.usermanager".user_exists;
22 local st = require "util.stanza";
23 local storagemanager = require "core.storagemanager";
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, nil, jid) 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, nil, jid) 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 roster_store = storagemanager.open(host, "roster", "keyval");
113 local data, err = roster_store:get(username);
115 if user then user.roster = roster; end
116 roster_metadata(roster, err);
119 log("warn", "roster for %s has a self-contact", jid);
122 hosts[host].events.fire_event("roster-load", { username = username, host = host, roster = roster });
127 function save_roster(username, host, roster, jid)
128 if not um_user_exists(username, host) then
129 log("debug", "not saving roster for %s@%s: the user doesn't exist", username, host);
133 log("debug", "save_roster: saving roster for %s@%s, (%s)", username, host, jid or "all contacts");
135 roster = hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster;
137 -- --roster = load_roster(username, host);
138 -- return true; -- roster unchanged, no reason to save
142 local metadata = roster_metadata(roster);
143 if metadata.version ~= true then
144 metadata.version = (metadata.version or 0) + 1;
146 if metadata.broken then return nil, "Not saving broken roster" end
148 local roster_store = storagemanager.open(host, "roster", "keyval");
149 return roster_store:set(username, roster);
151 local roster_store = storagemanager.open(host, "roster", "map");
152 return roster_store:set_keys(username, { [false] = metadata, [jid] = roster[jid] or roster_store.remove });
155 log("warn", "save_roster: user had no roster to save");
159 local function process_inbound_subscription_approval(username, host, jid)
160 local roster = load_roster(username, host);
161 local item = roster[jid];
162 if item and item.ask then
163 if item.subscription == "none" then
164 item.subscription = "to";
165 else -- subscription == from
166 item.subscription = "both";
169 return save_roster(username, host, roster, jid);
173 local is_contact_pending_out -- forward declaration
175 local function process_inbound_subscription_cancellation(username, host, jid)
176 local roster = load_roster(username, host);
177 local item = roster[jid];
179 if is_contact_pending_out(username, host, jid) then
184 if item.subscription == "to" then
185 item.subscription = "none";
187 elseif item.subscription == "both" then
188 item.subscription = "from";
193 return save_roster(username, host, roster, jid);
197 local is_contact_pending_in -- forward declaration
199 local function process_inbound_unsubscribe(username, host, jid)
200 local roster = load_roster(username, host);
201 local item = roster[jid];
203 if is_contact_pending_in(username, host, jid) then
204 roster[false].pending[jid] = nil;
208 if item.subscription == "from" then
209 item.subscription = "none";
211 elseif item.subscription == "both" then
212 item.subscription = "to";
217 return save_roster(username, host, roster, jid);
221 local function _get_online_roster_subscription(jidA, jidB)
222 local user = bare_sessions[jidA];
223 local item = user and (user.roster[jidB] or { subscription = "none" });
224 return item and item.subscription;
226 local function is_contact_subscribed(username, host, jid)
228 local selfjid = username.."@"..host;
229 local user_subscription = _get_online_roster_subscription(selfjid, jid);
230 if user_subscription then return (user_subscription == "both" or user_subscription == "from"); end
231 local contact_subscription = _get_online_roster_subscription(jid, selfjid);
232 if contact_subscription then return (contact_subscription == "both" or contact_subscription == "to"); end
234 local roster, err = load_roster(username, host);
235 local item = roster[jid];
236 return item and (item.subscription == "from" or item.subscription == "both"), err;
239 function is_contact_pending_in(username, host, jid)
240 local roster = load_roster(username, host);
241 return roster[false].pending[jid];
243 local function set_contact_pending_in(username, host, jid)
244 local roster = load_roster(username, host);
245 local item = roster[jid];
246 if item and (item.subscription == "from" or item.subscription == "both") then
249 roster[false].pending[jid] = true;
250 return save_roster(username, host, roster, jid);
252 function is_contact_pending_out(username, host, jid)
253 local roster = load_roster(username, host);
254 local item = roster[jid];
255 return item and item.ask;
257 local function set_contact_pending_out(username, host, jid) -- subscribe
258 local roster = load_roster(username, host);
259 local item = roster[jid];
260 if item and (item.ask or item.subscription == "to" or item.subscription == "both") then
264 item = {subscription = "none", groups = {}};
267 item.ask = "subscribe";
268 log("debug", "set_contact_pending_out: saving roster; set %s@%s.roster[%q].ask=subscribe", username, host, jid);
269 return save_roster(username, host, roster, jid);
271 local function unsubscribe(username, host, jid)
272 local roster = load_roster(username, host);
273 local item = roster[jid];
274 if not item then return false; end
275 if (item.subscription == "from" or item.subscription == "none") and not item.ask then
279 if item.subscription == "both" then
280 item.subscription = "from";
281 elseif item.subscription == "to" then
282 item.subscription = "none";
284 return save_roster(username, host, roster, jid);
286 local function subscribed(username, host, jid)
287 if is_contact_pending_in(username, host, jid) then
288 local roster = load_roster(username, host);
289 local item = roster[jid];
290 if not item then -- FIXME should roster item be auto-created?
291 item = {subscription = "none", groups = {}};
294 if item.subscription == "none" then
295 item.subscription = "from";
296 else -- subscription == to
297 item.subscription = "both";
299 roster[false].pending[jid] = nil;
300 return save_roster(username, host, roster, jid);
301 end -- TODO else implement optional feature pre-approval (ask = subscribed)
303 local function unsubscribed(username, host, jid)
304 local roster = load_roster(username, host);
305 local item = roster[jid];
306 local pending = is_contact_pending_in(username, host, jid);
308 roster[false].pending[jid] = nil;
312 if item.subscription == "from" then
313 item.subscription = "none";
314 is_subscribed = true;
315 elseif item.subscription == "both" then
316 item.subscription = "to";
317 is_subscribed = true;
320 local success = (pending or is_subscribed) and save_roster(username, host, roster, jid);
321 return success, pending, subscribed;
324 local function process_outbound_subscription_request(username, host, jid)
325 local roster = load_roster(username, host);
326 local item = roster[jid];
327 if item and (item.subscription == "none" or item.subscription == "from") then
328 item.ask = "subscribe";
329 return save_roster(username, host, roster, jid);
333 --[[function process_outbound_subscription_approval(username, host, jid)
334 local roster = load_roster(username, host);
335 local item = roster[jid];
336 if item and (item.subscription == "none" or item.subscription == "from" then
337 item.ask = "subscribe";
338 return save_roster(username, host, roster);
345 add_to_roster = add_to_roster;
346 remove_from_roster = remove_from_roster;
347 roster_push = roster_push;
348 load_roster = load_roster;
349 save_roster = save_roster;
350 process_inbound_subscription_approval = process_inbound_subscription_approval;
351 process_inbound_subscription_cancellation = process_inbound_subscription_cancellation;
352 process_inbound_unsubscribe = process_inbound_unsubscribe;
353 is_contact_subscribed = is_contact_subscribed;
354 is_contact_pending_in = is_contact_pending_in;
355 set_contact_pending_in = set_contact_pending_in;
356 is_contact_pending_out = is_contact_pending_out;
357 set_contact_pending_out = set_contact_pending_out;
358 unsubscribe = unsubscribe;
359 subscribed = subscribed;
360 unsubscribed = unsubscribed;
361 process_outbound_subscription_request = process_outbound_subscription_request;