2 -- Copyright (C) 2008-2009 Matthew Wild
3 -- Copyright (C) 2008-2009 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");
14 local setmetatable = setmetatable;
15 local format = string.format;
16 local loadfile, setfenv, pcall = loadfile, setfenv, pcall;
17 local pairs, ipairs = pairs, ipairs;
18 local tostring = tostring;
22 local datamanager = require "util.datamanager"
23 local st = require "util.stanza";
25 module "rostermanager"
27 function add_to_roster(session, jid, item)
28 if session.roster then
29 local old_item = session.roster[jid];
30 session.roster[jid] = item;
31 if save_roster(session.username, session.host) then
34 session.roster[jid] = old_item;
35 return nil, "wait", "internal-server-error", "Unable to save roster";
38 return nil, "auth", "not-authorized", "Session's roster not loaded";
42 function remove_from_roster(session, jid)
43 if session.roster then
44 local old_item = session.roster[jid];
45 session.roster[jid] = nil;
46 if save_roster(session.username, session.host) then
49 session.roster[jid] = old_item;
50 return nil, "wait", "internal-server-error", "Unable to save roster";
53 return nil, "auth", "not-authorized", "Session's roster not loaded";
57 function roster_push(username, host, jid)
58 local roster = jid and jid ~= "pending" and hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster;
60 local item = hosts[host].sessions[username].roster[jid];
61 local stanza = st.iq({type="set"});
62 stanza:tag("query", {xmlns = "jabber:iq:roster", ver = tostring(roster[false].version or "1") });
64 stanza:tag("item", {jid = jid, subscription = item.subscription, name = item.name, ask = item.ask});
65 for group in pairs(item.groups) do
66 stanza:tag("group"):text(group):up();
69 stanza:tag("item", {jid = jid, subscription = "remove"});
71 stanza:up(); -- move out from item
72 stanza:up(); -- move out from stanza
74 for _, session in pairs(hosts[host].sessions[username].sessions) do
75 if session.interested then
76 -- FIXME do we need to set stanza.attr.to?
83 function load_roster(username, host)
84 local jid = username.."@"..host;
85 log("debug", "load_roster: asked for: "..jid);
87 if hosts[host] and hosts[host].sessions[username] then
88 roster = hosts[host].sessions[username].roster;
90 log("debug", "load_roster: loading for new user: "..username.."@"..host);
91 roster = datamanager.load(username, host, "roster") or {};
92 if not roster[false] then roster[false] = { }; end
95 log("warn", "roster for "..jid.." has a self-contact");
97 hosts[host].sessions[username].roster = roster;
98 hosts[host].events.fire_event("roster-load", username, host, roster);
103 -- Attempt to load roster for non-loaded user
104 log("debug", "load_roster: loading for offline user: "..username.."@"..host);
105 roster = datamanager.load(username, host, "roster") or {};
106 if not roster[false] then roster[false] = { }; end
109 log("warn", "roster for "..jid.." has a self-contact");
111 hosts[host].events.fire_event("roster-load", username, host, roster);
115 function save_roster(username, host, roster)
116 log("debug", "save_roster: saving roster for "..username.."@"..host);
118 roster = hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster;
120 -- --roster = load_roster(username, host);
121 -- return true; -- roster unchanged, no reason to save
125 if not roster[false] then roster[false] = {}; end
126 roster[false].version = (roster[false].version or 0) + 1;
127 return datamanager.store(username, host, "roster", roster);
129 log("warn", "save_roster: user had no roster to save");
133 function process_inbound_subscription_approval(username, host, jid)
134 local roster = load_roster(username, host);
135 local item = roster[jid];
136 if item and item.ask then
137 if item.subscription == "none" then
138 item.subscription = "to";
139 else -- subscription == from
140 item.subscription = "both";
143 return save_roster(username, host, roster);
147 function process_inbound_subscription_cancellation(username, host, jid)
148 local roster = load_roster(username, host);
149 local item = roster[jid];
151 if is_contact_pending_out(username, host, jid) then
156 if item.subscription == "to" then
157 item.subscription = "none";
159 elseif item.subscription == "both" then
160 item.subscription = "from";
165 return save_roster(username, host, roster);
169 function process_inbound_unsubscribe(username, host, jid)
170 local roster = load_roster(username, host);
171 local item = roster[jid];
173 if is_contact_pending_in(username, host, jid) then
174 roster.pending[jid] = nil; -- TODO maybe delete roster.pending if empty?
178 if item.subscription == "from" then
179 item.subscription = "none";
181 elseif item.subscription == "both" then
182 item.subscription = "to";
187 return save_roster(username, host, roster);
191 function is_contact_subscribed(username, host, jid)
192 local roster = load_roster(username, host);
193 local item = roster[jid];
194 return item and (item.subscription == "from" or item.subscription == "both");
197 function is_contact_pending_in(username, host, jid)
198 local roster = load_roster(username, host);
199 return roster.pending and roster.pending[jid];
201 function set_contact_pending_in(username, host, jid, pending)
202 local roster = load_roster(username, host);
203 local item = roster[jid];
204 if item and (item.subscription == "from" or item.subscription == "both") then
207 if not roster.pending then roster.pending = {}; end
208 roster.pending[jid] = true;
209 return save_roster(username, host, roster);
211 function is_contact_pending_out(username, host, jid)
212 local roster = load_roster(username, host);
213 local item = roster[jid];
214 return item and item.ask;
216 function set_contact_pending_out(username, host, jid) -- subscribe
217 local roster = load_roster(username, host);
218 local item = roster[jid];
219 if item and (item.ask or item.subscription == "to" or item.subscription == "both") then
223 item = {subscription = "none", groups = {}};
226 item.ask = "subscribe";
227 log("debug", "set_contact_pending_out: saving roster; set "..username.."@"..host..".roster["..jid.."].ask=subscribe");
228 return save_roster(username, host, roster);
230 function unsubscribe(username, host, jid)
231 local roster = load_roster(username, host);
232 local item = roster[jid];
233 if not item then return false; end
234 if (item.subscription == "from" or item.subscription == "none") and not item.ask then
238 if item.subscription == "both" then
239 item.subscription = "from";
240 elseif item.subscription == "to" then
241 item.subscription = "none";
243 return save_roster(username, host, roster);
245 function subscribed(username, host, jid)
246 if is_contact_pending_in(username, host, jid) then
247 local roster = load_roster(username, host);
248 local item = roster[jid];
249 if not item then -- FIXME should roster item be auto-created?
250 item = {subscription = "none", groups = {}};
253 if item.subscription == "none" then
254 item.subscription = "from";
255 else -- subscription == to
256 item.subscription = "both";
258 roster.pending[jid] = nil;
259 -- TODO maybe remove roster.pending if empty
260 return save_roster(username, host, roster);
261 end -- TODO else implement optional feature pre-approval (ask = subscribed)
263 function unsubscribed(username, host, jid)
264 local roster = load_roster(username, host);
265 local item = roster[jid];
266 local pending = is_contact_pending_in(username, host, jid);
268 if is_contact_pending_in(username, host, jid) then
269 roster.pending[jid] = nil; -- TODO maybe delete roster.pending if empty?
273 if item.subscription == "from" then
274 item.subscription = "none";
276 elseif item.subscription == "both" then
277 item.subscription = "to";
282 return save_roster(username, host, roster);
286 function process_outbound_subscription_request(username, host, jid)
287 local roster = load_roster(username, host);
288 local item = roster[jid];
289 if item and (item.subscription == "none" or item.subscription == "from") then
290 item.ask = "subscribe";
291 return save_roster(username, host, roster);
295 --[[function process_outbound_subscription_approval(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);