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");
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 local metadata = roster[false];
120 roster[false] = metadata;
122 if metadata.version ~= true then
123 metadata.version = (metadata.version or 0) + 1;
125 return datamanager.store(username, host, "roster", roster);
127 log("warn", "save_roster: user had no roster to save");
131 function process_inbound_subscription_approval(username, host, jid)
132 local roster = load_roster(username, host);
133 local item = roster[jid];
134 if item and item.ask then
135 if item.subscription == "none" then
136 item.subscription = "to";
137 else -- subscription == from
138 item.subscription = "both";
141 return save_roster(username, host, roster);
145 function process_inbound_subscription_cancellation(username, host, jid)
146 local roster = load_roster(username, host);
147 local item = roster[jid];
149 if is_contact_pending_out(username, host, jid) then
154 if item.subscription == "to" then
155 item.subscription = "none";
157 elseif item.subscription == "both" then
158 item.subscription = "from";
163 return save_roster(username, host, roster);
167 function process_inbound_unsubscribe(username, host, jid)
168 local roster = load_roster(username, host);
169 local item = roster[jid];
171 if is_contact_pending_in(username, host, jid) then
172 roster.pending[jid] = nil; -- TODO maybe delete roster.pending if empty?
176 if item.subscription == "from" then
177 item.subscription = "none";
179 elseif item.subscription == "both" then
180 item.subscription = "to";
185 return save_roster(username, host, roster);
189 function is_contact_subscribed(username, host, jid)
190 local roster = load_roster(username, host);
191 local item = roster[jid];
192 return item and (item.subscription == "from" or item.subscription == "both");
195 function is_contact_pending_in(username, host, jid)
196 local roster = load_roster(username, host);
197 return roster.pending and roster.pending[jid];
199 function set_contact_pending_in(username, host, jid, pending)
200 local roster = load_roster(username, host);
201 local item = roster[jid];
202 if item and (item.subscription == "from" or item.subscription == "both") then
205 if not roster.pending then roster.pending = {}; end
206 roster.pending[jid] = true;
207 return save_roster(username, host, roster);
209 function is_contact_pending_out(username, host, jid)
210 local roster = load_roster(username, host);
211 local item = roster[jid];
212 return item and item.ask;
214 function set_contact_pending_out(username, host, jid) -- subscribe
215 local roster = load_roster(username, host);
216 local item = roster[jid];
217 if item and (item.ask or item.subscription == "to" or item.subscription == "both") then
221 item = {subscription = "none", groups = {}};
224 item.ask = "subscribe";
225 log("debug", "set_contact_pending_out: saving roster; set "..username.."@"..host..".roster["..jid.."].ask=subscribe");
226 return save_roster(username, host, roster);
228 function unsubscribe(username, host, jid)
229 local roster = load_roster(username, host);
230 local item = roster[jid];
231 if not item then return false; end
232 if (item.subscription == "from" or item.subscription == "none") and not item.ask then
236 if item.subscription == "both" then
237 item.subscription = "from";
238 elseif item.subscription == "to" then
239 item.subscription = "none";
241 return save_roster(username, host, roster);
243 function subscribed(username, host, jid)
244 if is_contact_pending_in(username, host, jid) then
245 local roster = load_roster(username, host);
246 local item = roster[jid];
247 if not item then -- FIXME should roster item be auto-created?
248 item = {subscription = "none", groups = {}};
251 if item.subscription == "none" then
252 item.subscription = "from";
253 else -- subscription == to
254 item.subscription = "both";
256 roster.pending[jid] = nil;
257 -- TODO maybe remove roster.pending if empty
258 return save_roster(username, host, roster);
259 end -- TODO else implement optional feature pre-approval (ask = subscribed)
261 function unsubscribed(username, host, jid)
262 local roster = load_roster(username, host);
263 local item = roster[jid];
264 local pending = is_contact_pending_in(username, host, jid);
266 if is_contact_pending_in(username, host, jid) then
267 roster.pending[jid] = nil; -- TODO maybe delete roster.pending if empty?
271 if item.subscription == "from" then
272 item.subscription = "none";
274 elseif item.subscription == "both" then
275 item.subscription = "to";
280 return save_roster(username, host, roster);
284 function process_outbound_subscription_request(username, host, jid)
285 local roster = load_roster(username, host);
286 local item = roster[jid];
287 if item and (item.subscription == "none" or item.subscription == "from") then
288 item.ask = "subscribe";
289 return save_roster(username, host, roster);
293 --[[function process_outbound_subscription_approval(username, host, jid)
294 local roster = load_roster(username, host);
295 local item = roster[jid];
296 if item and (item.subscription == "none" or item.subscription == "from" then
297 item.ask = "subscribe";
298 return save_roster(username, host, roster);