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";
26 local save_roster; -- forward declaration
28 local 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
35 session.roster[jid] = old_item;
36 return nil, "wait", "internal-server-error", "Unable to save roster";
39 return nil, "auth", "not-authorized", "Session's roster not loaded";
43 local 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
50 session.roster[jid] = old_item;
51 return nil, "wait", "internal-server-error", "Unable to save roster";
54 return nil, "auth", "not-authorized", "Session's roster not loaded";
58 local function roster_push(username, host, jid)
59 local roster = jid and hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster;
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") });
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();
70 stanza:tag("item", {jid = jid, subscription = "remove"});
72 stanza:up(); -- move out from item
73 stanza:up(); -- move out from stanza
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?
84 local function roster_metadata(roster, err)
85 local metadata = roster[false];
87 metadata = { broken = err or nil };
88 roster[false] = metadata;
90 if roster.pending and type(roster.pending.subscription) ~= "string" then
91 metadata.pending = roster.pending;
93 elseif not metadata.pending then
94 metadata.pending = {};
99 local function load_roster(username, host)
100 local jid = username.."@"..host;
101 log("debug", "load_roster: asked for: %s", jid);
102 local user = bare_sessions[jid];
105 roster = user.roster;
106 if roster then return roster; end
107 log("debug", "load_roster: loading for new user: %s@%s", username, host);
108 else -- Attempt to load roster for non-loaded user
109 log("debug", "load_roster: loading for offline user: %s@%s", username, host);
111 local roster_store = require "core.storagemanager".open(host, "roster", "keyval");
112 local data, err = roster_store:get(username);
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 local roster_store = require "core.storagemanager".open(host, "roster", "keyval");
147 return roster_store:set(username, roster);
149 log("warn", "save_roster: user had no roster to save");
153 local function process_inbound_subscription_approval(username, host, jid)
154 local roster = load_roster(username, host);
155 local item = roster[jid];
156 if item and item.ask then
157 if item.subscription == "none" then
158 item.subscription = "to";
159 else -- subscription == from
160 item.subscription = "both";
163 return save_roster(username, host, roster);
167 local is_contact_pending_out -- forward declaration
169 local function process_inbound_subscription_cancellation(username, host, jid)
170 local roster = load_roster(username, host);
171 local item = roster[jid];
173 if is_contact_pending_out(username, host, jid) then
178 if item.subscription == "to" then
179 item.subscription = "none";
181 elseif item.subscription == "both" then
182 item.subscription = "from";
187 return save_roster(username, host, roster);
191 local is_contact_pending_in -- forward declaration
193 local function process_inbound_unsubscribe(username, host, jid)
194 local roster = load_roster(username, host);
195 local item = roster[jid];
197 if is_contact_pending_in(username, host, jid) then
198 roster[false].pending[jid] = nil;
202 if item.subscription == "from" then
203 item.subscription = "none";
205 elseif item.subscription == "both" then
206 item.subscription = "to";
211 return save_roster(username, host, roster);
215 local function _get_online_roster_subscription(jidA, jidB)
216 local user = bare_sessions[jidA];
217 local item = user and (user.roster[jidB] or { subscription = "none" });
218 return item and item.subscription;
220 local function is_contact_subscribed(username, host, jid)
222 local selfjid = username.."@"..host;
223 local user_subscription = _get_online_roster_subscription(selfjid, jid);
224 if user_subscription then return (user_subscription == "both" or user_subscription == "from"); end
225 local contact_subscription = _get_online_roster_subscription(jid, selfjid);
226 if contact_subscription then return (contact_subscription == "both" or contact_subscription == "to"); end
228 local roster, err = load_roster(username, host);
229 local item = roster[jid];
230 return item and (item.subscription == "from" or item.subscription == "both"), err;
233 function is_contact_pending_in(username, host, jid)
234 local roster = load_roster(username, host);
235 return roster[false].pending[jid];
237 local function set_contact_pending_in(username, host, jid)
238 local roster = load_roster(username, host);
239 local item = roster[jid];
240 if item and (item.subscription == "from" or item.subscription == "both") then
243 roster[false].pending[jid] = true;
244 return save_roster(username, host, roster);
246 function is_contact_pending_out(username, host, jid)
247 local roster = load_roster(username, host);
248 local item = roster[jid];
249 return item and item.ask;
251 local function set_contact_pending_out(username, host, jid) -- subscribe
252 local roster = load_roster(username, host);
253 local item = roster[jid];
254 if item and (item.ask or item.subscription == "to" or item.subscription == "both") then
258 item = {subscription = "none", groups = {}};
261 item.ask = "subscribe";
262 log("debug", "set_contact_pending_out: saving roster; set %s@%s.roster[%q].ask=subscribe", username, host, jid);
263 return save_roster(username, host, roster);
265 local function unsubscribe(username, host, jid)
266 local roster = load_roster(username, host);
267 local item = roster[jid];
268 if not item then return false; end
269 if (item.subscription == "from" or item.subscription == "none") and not item.ask then
273 if item.subscription == "both" then
274 item.subscription = "from";
275 elseif item.subscription == "to" then
276 item.subscription = "none";
278 return save_roster(username, host, roster);
280 local function subscribed(username, host, jid)
281 if is_contact_pending_in(username, host, jid) then
282 local roster = load_roster(username, host);
283 local item = roster[jid];
284 if not item then -- FIXME should roster item be auto-created?
285 item = {subscription = "none", groups = {}};
288 if item.subscription == "none" then
289 item.subscription = "from";
290 else -- subscription == to
291 item.subscription = "both";
293 roster[false].pending[jid] = nil;
294 return save_roster(username, host, roster);
295 end -- TODO else implement optional feature pre-approval (ask = subscribed)
297 local function unsubscribed(username, host, jid)
298 local roster = load_roster(username, host);
299 local item = roster[jid];
300 local pending = is_contact_pending_in(username, host, jid);
302 roster[false].pending[jid] = nil;
306 if item.subscription == "from" then
307 item.subscription = "none";
308 is_subscribed = true;
309 elseif item.subscription == "both" then
310 item.subscription = "to";
311 is_subscribed = true;
314 local success = (pending or is_subscribed) and save_roster(username, host, roster);
315 return success, pending, subscribed;
318 local function process_outbound_subscription_request(username, host, jid)
319 local roster = load_roster(username, host);
320 local item = roster[jid];
321 if item and (item.subscription == "none" or item.subscription == "from") then
322 item.ask = "subscribe";
323 return save_roster(username, host, roster);
327 --[[function process_outbound_subscription_approval(username, host, jid)
328 local roster = load_roster(username, host);
329 local item = roster[jid];
330 if item and (item.subscription == "none" or item.subscription == "from" then
331 item.ask = "subscribe";
332 return save_roster(username, host, roster);
339 add_to_roster = add_to_roster;
340 remove_from_roster = remove_from_roster;
341 roster_push = roster_push;
342 load_roster = load_roster;
343 save_roster = save_roster;
344 process_inbound_subscription_approval = process_inbound_subscription_approval;
345 process_inbound_subscription_cancellation = process_inbound_subscription_cancellation;
346 process_inbound_unsubscribe = process_inbound_unsubscribe;
347 is_contact_subscribed = is_contact_subscribed;
348 is_contact_pending_in = is_contact_pending_in;
349 set_contact_pending_in = set_contact_pending_in;
350 is_contact_pending_out = is_contact_pending_out;
351 set_contact_pending_out = set_contact_pending_out;
352 unsubscribe = unsubscribe;
353 subscribed = subscribed;
354 unsubscribed = unsubscribed;
355 process_outbound_subscription_request = process_outbound_subscription_request;