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;
21 local bare_sessions = bare_sessions;
23 local datamanager = require "util.datamanager"
24 local st = require "util.stanza";
26 module "rostermanager"
28 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 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 function roster_push(username, host, jid)
59 local roster = jid and jid ~= "pending" 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 function load_roster(username, host)
85 local jid = username.."@"..host;
86 log("debug", "load_roster: asked for: "..jid);
87 local user = bare_sessions[jid];
91 if roster then return roster; end
92 log("debug", "load_roster: loading for new user: "..username.."@"..host);
93 else -- Attempt to load roster for non-loaded user
94 log("debug", "load_roster: loading for offline user: "..username.."@"..host);
96 roster = datamanager.load(username, host, "roster") or {};
97 if user then user.roster = roster; end
98 if not roster[false] then roster[false] = { }; end
101 log("warn", "roster for "..jid.." has a self-contact");
103 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 "..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 if not roster[false] then roster[false] = {}; end
118 roster[false].version = (roster[false].version or 0) + 1;
119 return datamanager.store(username, host, "roster", roster);
121 log("warn", "save_roster: user had no roster to save");
125 function process_inbound_subscription_approval(username, host, jid)
126 local roster = load_roster(username, host);
127 local item = roster[jid];
128 if item and item.ask then
129 if item.subscription == "none" then
130 item.subscription = "to";
131 else -- subscription == from
132 item.subscription = "both";
135 return save_roster(username, host, roster);
139 function process_inbound_subscription_cancellation(username, host, jid)
140 local roster = load_roster(username, host);
141 local item = roster[jid];
143 if is_contact_pending_out(username, host, jid) then
148 if item.subscription == "to" then
149 item.subscription = "none";
151 elseif item.subscription == "both" then
152 item.subscription = "from";
157 return save_roster(username, host, roster);
161 function process_inbound_unsubscribe(username, host, jid)
162 local roster = load_roster(username, host);
163 local item = roster[jid];
165 if is_contact_pending_in(username, host, jid) then
166 roster.pending[jid] = nil; -- TODO maybe delete roster.pending if empty?
170 if item.subscription == "from" then
171 item.subscription = "none";
173 elseif item.subscription == "both" then
174 item.subscription = "to";
179 return save_roster(username, host, roster);
183 function is_contact_subscribed(username, host, jid)
184 local roster = load_roster(username, host);
185 local item = roster[jid];
186 return item and (item.subscription == "from" or item.subscription == "both");
189 function is_contact_pending_in(username, host, jid)
190 local roster = load_roster(username, host);
191 return roster.pending and roster.pending[jid];
193 function set_contact_pending_in(username, host, jid, pending)
194 local roster = load_roster(username, host);
195 local item = roster[jid];
196 if item and (item.subscription == "from" or item.subscription == "both") then
199 if not roster.pending then roster.pending = {}; end
200 roster.pending[jid] = true;
201 return save_roster(username, host, roster);
203 function is_contact_pending_out(username, host, jid)
204 local roster = load_roster(username, host);
205 local item = roster[jid];
206 return item and item.ask;
208 function set_contact_pending_out(username, host, jid) -- subscribe
209 local roster = load_roster(username, host);
210 local item = roster[jid];
211 if item and (item.ask or item.subscription == "to" or item.subscription == "both") then
215 item = {subscription = "none", groups = {}};
218 item.ask = "subscribe";
219 log("debug", "set_contact_pending_out: saving roster; set "..username.."@"..host..".roster["..jid.."].ask=subscribe");
220 return save_roster(username, host, roster);
222 function unsubscribe(username, host, jid)
223 local roster = load_roster(username, host);
224 local item = roster[jid];
225 if not item then return false; end
226 if (item.subscription == "from" or item.subscription == "none") and not item.ask then
230 if item.subscription == "both" then
231 item.subscription = "from";
232 elseif item.subscription == "to" then
233 item.subscription = "none";
235 return save_roster(username, host, roster);
237 function subscribed(username, host, jid)
238 if is_contact_pending_in(username, host, jid) then
239 local roster = load_roster(username, host);
240 local item = roster[jid];
241 if not item then -- FIXME should roster item be auto-created?
242 item = {subscription = "none", groups = {}};
245 if item.subscription == "none" then
246 item.subscription = "from";
247 else -- subscription == to
248 item.subscription = "both";
250 roster.pending[jid] = nil;
251 -- TODO maybe remove roster.pending if empty
252 return save_roster(username, host, roster);
253 end -- TODO else implement optional feature pre-approval (ask = subscribed)
255 function unsubscribed(username, host, jid)
256 local roster = load_roster(username, host);
257 local item = roster[jid];
258 local pending = is_contact_pending_in(username, host, jid);
260 if is_contact_pending_in(username, host, jid) then
261 roster.pending[jid] = nil; -- TODO maybe delete roster.pending if empty?
265 if item.subscription == "from" then
266 item.subscription = "none";
268 elseif item.subscription == "both" then
269 item.subscription = "to";
274 return save_roster(username, host, roster);
278 function process_outbound_subscription_request(username, host, jid)
279 local roster = load_roster(username, host);
280 local item = roster[jid];
281 if item and (item.subscription == "none" or item.subscription == "from") then
282 item.ask = "subscribe";
283 return save_roster(username, host, roster);
287 --[[function process_outbound_subscription_approval(username, host, jid)
288 local roster = load_roster(username, host);
289 local item = roster[jid];
290 if item and (item.subscription == "none" or item.subscription == "from" then
291 item.ask = "subscribe";
292 return save_roster(username, host, roster);