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 st = require "util.stanza";
23 module "rostermanager"
25 function add_to_roster(session, jid, item)
26 if session.roster then
27 local old_item = session.roster[jid];
28 session.roster[jid] = item;
29 if save_roster(session.username, session.host) then
32 session.roster[jid] = old_item;
33 return nil, "wait", "internal-server-error", "Unable to save roster";
36 return nil, "auth", "not-authorized", "Session's roster not loaded";
40 function remove_from_roster(session, jid)
41 if session.roster then
42 local old_item = session.roster[jid];
43 session.roster[jid] = nil;
44 if save_roster(session.username, session.host) then
47 session.roster[jid] = old_item;
48 return nil, "wait", "internal-server-error", "Unable to save roster";
51 return nil, "auth", "not-authorized", "Session's roster not loaded";
55 function roster_push(username, host, jid)
56 local roster = jid and jid ~= "pending" and hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster;
58 local item = hosts[host].sessions[username].roster[jid];
59 local stanza = st.iq({type="set"});
60 stanza:tag("query", {xmlns = "jabber:iq:roster", ver = tostring(roster[false].version or "1") });
62 stanza:tag("item", {jid = jid, subscription = item.subscription, name = item.name, ask = item.ask});
63 for group in pairs(item.groups) do
64 stanza:tag("group"):text(group):up();
67 stanza:tag("item", {jid = jid, subscription = "remove"});
69 stanza:up(); -- move out from item
70 stanza:up(); -- move out from stanza
72 for _, session in pairs(hosts[host].sessions[username].sessions) do
73 if session.interested then
74 -- FIXME do we need to set stanza.attr.to?
81 function load_roster(username, host)
82 local jid = username.."@"..host;
83 log("debug", "load_roster: asked for: %s", jid);
84 local user = bare_sessions[jid];
88 if roster then return roster; end
89 log("debug", "load_roster: loading for new user: %s@%s", username, host);
90 else -- Attempt to load roster for non-loaded user
91 log("debug", "load_roster: loading for offline user: %s@%s", username, host);
93 local data, err = datamanager.load(username, host, "roster");
95 if user then user.roster = roster; end
96 if not roster[false] then roster[false] = { broken = err or nil }; end
99 log("warn", "roster for %s has a self-contact", jid);
102 hosts[host].events.fire_event("roster-load", username, host, roster);
107 function save_roster(username, host, roster)
108 log("debug", "save_roster: saving roster for %s@%s", username, host);
110 roster = hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster;
112 -- --roster = load_roster(username, host);
113 -- return true; -- roster unchanged, no reason to save
117 local metadata = roster[false];
120 roster[false] = metadata;
122 if metadata.version ~= true then
123 metadata.version = (metadata.version or 0) + 1;
125 if roster[false].broken then return nil, "Not saving broken roster" end
126 return datamanager.store(username, host, "roster", roster);
128 log("warn", "save_roster: user had no roster to save");
132 function process_inbound_subscription_approval(username, host, jid)
133 local roster = load_roster(username, host);
134 local item = roster[jid];
135 if item and item.ask then
136 if item.subscription == "none" then
137 item.subscription = "to";
138 else -- subscription == from
139 item.subscription = "both";
142 return save_roster(username, host, roster);
146 function process_inbound_subscription_cancellation(username, host, jid)
147 local roster = load_roster(username, host);
148 local item = roster[jid];
150 if is_contact_pending_out(username, host, jid) then
155 if item.subscription == "to" then
156 item.subscription = "none";
158 elseif item.subscription == "both" then
159 item.subscription = "from";
164 return save_roster(username, host, roster);
168 function process_inbound_unsubscribe(username, host, jid)
169 local roster = load_roster(username, host);
170 local item = roster[jid];
172 if is_contact_pending_in(username, host, jid) then
173 roster.pending[jid] = nil; -- TODO maybe delete roster.pending if empty?
177 if item.subscription == "from" then
178 item.subscription = "none";
180 elseif item.subscription == "both" then
181 item.subscription = "to";
186 return save_roster(username, host, roster);
190 local function _get_online_roster_subscription(jidA, jidB)
191 local user = bare_sessions[jidA];
192 local item = user and (user.roster[jidB] or { subscription = "none" });
193 return item and item.subscription;
195 function is_contact_subscribed(username, host, jid)
197 local selfjid = username.."@"..host;
198 local subscription = _get_online_roster_subscription(selfjid, jid);
199 if subscription then return (subscription == "both" or subscription == "from"); end
200 local subscription = _get_online_roster_subscription(jid, selfjid);
201 if subscription then return (subscription == "both" or subscription == "to"); end
203 local roster, err = load_roster(username, host);
204 local item = roster[jid];
205 return item and (item.subscription == "from" or item.subscription == "both"), err;
208 function is_contact_pending_in(username, host, jid)
209 local roster = load_roster(username, host);
210 return roster.pending and roster.pending[jid];
212 function set_contact_pending_in(username, host, jid, pending)
213 local roster = load_roster(username, host);
214 local item = roster[jid];
215 if item and (item.subscription == "from" or item.subscription == "both") then
218 if not roster.pending then roster.pending = {}; end
219 roster.pending[jid] = true;
220 return save_roster(username, host, roster);
222 function is_contact_pending_out(username, host, jid)
223 local roster = load_roster(username, host);
224 local item = roster[jid];
225 return item and item.ask;
227 function set_contact_pending_out(username, host, jid) -- subscribe
228 local roster = load_roster(username, host);
229 local item = roster[jid];
230 if item and (item.ask or item.subscription == "to" or item.subscription == "both") then
234 item = {subscription = "none", groups = {}};
237 item.ask = "subscribe";
238 log("debug", "set_contact_pending_out: saving roster; set %s@%s.roster[%q].ask=subscribe", username, host, jid);
239 return save_roster(username, host, roster);
241 function unsubscribe(username, host, jid)
242 local roster = load_roster(username, host);
243 local item = roster[jid];
244 if not item then return false; end
245 if (item.subscription == "from" or item.subscription == "none") and not item.ask then
249 if item.subscription == "both" then
250 item.subscription = "from";
251 elseif item.subscription == "to" then
252 item.subscription = "none";
254 return save_roster(username, host, roster);
256 function subscribed(username, host, jid)
257 if is_contact_pending_in(username, host, jid) then
258 local roster = load_roster(username, host);
259 local item = roster[jid];
260 if not item then -- FIXME should roster item be auto-created?
261 item = {subscription = "none", groups = {}};
264 if item.subscription == "none" then
265 item.subscription = "from";
266 else -- subscription == to
267 item.subscription = "both";
269 roster.pending[jid] = nil;
270 -- TODO maybe remove roster.pending if empty
271 return save_roster(username, host, roster);
272 end -- TODO else implement optional feature pre-approval (ask = subscribed)
274 function unsubscribed(username, host, jid)
275 local roster = load_roster(username, host);
276 local item = roster[jid];
277 local pending = is_contact_pending_in(username, host, jid);
279 roster.pending[jid] = nil; -- TODO maybe delete roster.pending if empty?
283 if item.subscription == "from" then
284 item.subscription = "none";
286 elseif item.subscription == "both" then
287 item.subscription = "to";
291 local success = (pending or subscribed) and save_roster(username, host, roster);
292 return success, pending, subscribed;
295 function process_outbound_subscription_request(username, host, jid)
296 local roster = load_roster(username, host);
297 local item = roster[jid];
298 if item and (item.subscription == "none" or item.subscription == "from") then
299 item.ask = "subscribe";
300 return save_roster(username, host, roster);
304 --[[function process_outbound_subscription_approval(username, host, jid)
305 local roster = load_roster(username, host);
306 local item = roster[jid];
307 if item and (item.subscription == "none" or item.subscription == "from" then
308 item.ask = "subscribe";
309 return save_roster(username, host, roster);