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;
18 local bare_sessions = bare_sessions;
20 local datamanager = require "util.datamanager"
21 local um_user_exists = require "core.usermanager".user_exists;
22 local st = require "util.stanza";
24 module "rostermanager"
26 function add_to_roster(session, jid, item)
27 if session.roster then
28 local old_item = session.roster[jid];
29 session.roster[jid] = item;
30 if save_roster(session.username, session.host) then
33 session.roster[jid] = old_item;
34 return nil, "wait", "internal-server-error", "Unable to save roster";
37 return nil, "auth", "not-authorized", "Session's roster not loaded";
41 function remove_from_roster(session, jid)
42 if session.roster then
43 local old_item = session.roster[jid];
44 session.roster[jid] = nil;
45 if save_roster(session.username, session.host) then
48 session.roster[jid] = old_item;
49 return nil, "wait", "internal-server-error", "Unable to save roster";
52 return nil, "auth", "not-authorized", "Session's roster not loaded";
56 function roster_push(username, host, jid)
57 local roster = jid and jid ~= "pending" and hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster;
59 local item = hosts[host].sessions[username].roster[jid];
60 local stanza = st.iq({type="set"});
61 stanza:tag("query", {xmlns = "jabber:iq:roster", ver = tostring(roster[false].version or "1") });
63 stanza:tag("item", {jid = jid, subscription = item.subscription, name = item.name, ask = item.ask});
64 for group in pairs(item.groups) do
65 stanza:tag("group"):text(group):up();
68 stanza:tag("item", {jid = jid, subscription = "remove"});
70 stanza:up(); -- move out from item
71 stanza:up(); -- move out from stanza
73 for _, session in pairs(hosts[host].sessions[username].sessions) do
74 if session.interested then
75 -- FIXME do we need to set stanza.attr.to?
82 function load_roster(username, host)
83 local jid = username.."@"..host;
84 log("debug", "load_roster: asked for: %s", jid);
85 local user = bare_sessions[jid];
89 if roster then return roster; end
90 log("debug", "load_roster: loading for new user: %s@%s", username, host);
91 else -- Attempt to load roster for non-loaded user
92 log("debug", "load_roster: loading for offline user: %s@%s", username, host);
94 local data, err = datamanager.load(username, host, "roster");
96 if user then user.roster = roster; end
97 if not roster[false] then roster[false] = { broken = err or nil }; end
100 log("warn", "roster for %s has a self-contact", jid);
103 hosts[host].events.fire_event("roster-load", { username = username, host = host, roster = roster });
108 function save_roster(username, host, roster)
109 if not um_user_exists(username, host) then
110 log("debug", "not saving roster for %s@%s: the user doesn't exist", username, host);
114 log("debug", "save_roster: saving roster for %s@%s", username, host);
116 roster = hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster;
118 -- --roster = load_roster(username, host);
119 -- return true; -- roster unchanged, no reason to save
123 local metadata = roster[false];
126 roster[false] = metadata;
128 if metadata.version ~= true then
129 metadata.version = (metadata.version or 0) + 1;
131 if roster[false].broken then return nil, "Not saving broken roster" end
132 return datamanager.store(username, host, "roster", roster);
134 log("warn", "save_roster: user had no roster to save");
138 function process_inbound_subscription_approval(username, host, jid)
139 local roster = load_roster(username, host);
140 local item = roster[jid];
141 if item and item.ask then
142 if item.subscription == "none" then
143 item.subscription = "to";
144 else -- subscription == from
145 item.subscription = "both";
148 return save_roster(username, host, roster);
152 function process_inbound_subscription_cancellation(username, host, jid)
153 local roster = load_roster(username, host);
154 local item = roster[jid];
156 if is_contact_pending_out(username, host, jid) then
161 if item.subscription == "to" then
162 item.subscription = "none";
164 elseif item.subscription == "both" then
165 item.subscription = "from";
170 return save_roster(username, host, roster);
174 function process_inbound_unsubscribe(username, host, jid)
175 local roster = load_roster(username, host);
176 local item = roster[jid];
178 if is_contact_pending_in(username, host, jid) then
179 roster.pending[jid] = nil; -- TODO maybe delete roster.pending if empty?
183 if item.subscription == "from" then
184 item.subscription = "none";
186 elseif item.subscription == "both" then
187 item.subscription = "to";
192 return save_roster(username, host, roster);
196 local function _get_online_roster_subscription(jidA, jidB)
197 local user = bare_sessions[jidA];
198 local item = user and (user.roster[jidB] or { subscription = "none" });
199 return item and item.subscription;
201 function is_contact_subscribed(username, host, jid)
203 local selfjid = username.."@"..host;
204 local subscription = _get_online_roster_subscription(selfjid, jid);
205 if subscription then return (subscription == "both" or subscription == "from"); end
206 local subscription = _get_online_roster_subscription(jid, selfjid);
207 if subscription then return (subscription == "both" or subscription == "to"); end
209 local roster, err = load_roster(username, host);
210 local item = roster[jid];
211 return item and (item.subscription == "from" or item.subscription == "both"), err;
214 function is_contact_pending_in(username, host, jid)
215 local roster = load_roster(username, host);
216 return roster.pending and roster.pending[jid];
218 function set_contact_pending_in(username, host, jid, pending)
219 local roster = load_roster(username, host);
220 local item = roster[jid];
221 if item and (item.subscription == "from" or item.subscription == "both") then
224 if not roster.pending then roster.pending = {}; end
225 roster.pending[jid] = true;
226 return save_roster(username, host, roster);
228 function is_contact_pending_out(username, host, jid)
229 local roster = load_roster(username, host);
230 local item = roster[jid];
231 return item and item.ask;
233 function set_contact_pending_out(username, host, jid) -- subscribe
234 local roster = load_roster(username, host);
235 local item = roster[jid];
236 if item and (item.ask or item.subscription == "to" or item.subscription == "both") then
240 item = {subscription = "none", groups = {}};
243 item.ask = "subscribe";
244 log("debug", "set_contact_pending_out: saving roster; set %s@%s.roster[%q].ask=subscribe", username, host, jid);
245 return save_roster(username, host, roster);
247 function unsubscribe(username, host, jid)
248 local roster = load_roster(username, host);
249 local item = roster[jid];
250 if not item then return false; end
251 if (item.subscription == "from" or item.subscription == "none") and not item.ask then
255 if item.subscription == "both" then
256 item.subscription = "from";
257 elseif item.subscription == "to" then
258 item.subscription = "none";
260 return save_roster(username, host, roster);
262 function subscribed(username, host, jid)
263 if is_contact_pending_in(username, host, jid) then
264 local roster = load_roster(username, host);
265 local item = roster[jid];
266 if not item then -- FIXME should roster item be auto-created?
267 item = {subscription = "none", groups = {}};
270 if item.subscription == "none" then
271 item.subscription = "from";
272 else -- subscription == to
273 item.subscription = "both";
275 roster.pending[jid] = nil;
276 -- TODO maybe remove roster.pending if empty
277 return save_roster(username, host, roster);
278 end -- TODO else implement optional feature pre-approval (ask = subscribed)
280 function unsubscribed(username, host, jid)
281 local roster = load_roster(username, host);
282 local item = roster[jid];
283 local pending = is_contact_pending_in(username, host, jid);
285 roster.pending[jid] = nil; -- TODO maybe delete roster.pending if empty?
289 if item.subscription == "from" then
290 item.subscription = "none";
292 elseif item.subscription == "both" then
293 item.subscription = "to";
297 local success = (pending or subscribed) and save_roster(username, host, roster);
298 return success, pending, subscribed;
301 function process_outbound_subscription_request(username, host, jid)
302 local roster = load_roster(username, host);
303 local item = roster[jid];
304 if item and (item.subscription == "none" or item.subscription == "from") then
305 item.ask = "subscribe";
306 return save_roster(username, host, roster);
310 --[[function process_outbound_subscription_approval(username, host, jid)
311 local roster = load_roster(username, host);
312 local item = roster[jid];
313 if item and (item.subscription == "none" or item.subscription == "from" then
314 item.ask = "subscribe";
315 return save_roster(username, host, roster);