rostermanager: Add missing import of `type()`
[prosody.git] / core / rostermanager.lua
1 -- Prosody IM
2 -- Copyright (C) 2008-2010 Matthew Wild
3 -- Copyright (C) 2008-2010 Waqas Hussain
4 --
5 -- This project is MIT/X11 licensed. Please see the
6 -- COPYING file in the source package for more information.
7 --
8
9
10
11
12 local log = require "util.logger".init("rostermanager");
13
14 local pairs = pairs;
15 local tostring = tostring;
16 local type = type;
17
18 local hosts = hosts;
19 local bare_sessions = prosody.bare_sessions;
20
21 local datamanager = require "util.datamanager"
22 local um_user_exists = require "core.usermanager".user_exists;
23 local st = require "util.stanza";
24
25 module "rostermanager"
26
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
32                         return true;
33                 else
34                         session.roster[jid] = old_item;
35                         return nil, "wait", "internal-server-error", "Unable to save roster";
36                 end
37         else
38                 return nil, "auth", "not-authorized", "Session's roster not loaded";
39         end
40 end
41
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
47                         return true;
48                 else
49                         session.roster[jid] = old_item;
50                         return nil, "wait", "internal-server-error", "Unable to save roster";
51                 end
52         else
53                 return nil, "auth", "not-authorized", "Session's roster not loaded";
54         end
55 end
56
57 function roster_push(username, host, jid)
58         local roster = jid and hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster;
59         if roster then
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")  });
63                 if item then
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();
67                         end
68                 else
69                         stanza:tag("item", {jid = jid, subscription = "remove"});
70                 end
71                 stanza:up(); -- move out from item
72                 stanza:up(); -- move out from stanza
73                 -- stanza ready
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?
77                                 session.send(stanza);
78                         end
79                 end
80         end
81 end
82
83 local function roster_metadata(roster, err)
84         local metadata = roster[false];
85         if not metadata then
86                 metadata = { broken = err or nil };
87                 roster[false] = metadata;
88         end
89         if not metadata.pending then
90                 if roster.pending and not type(roster.pending.subscription) == "string" then
91                         metadata.pending, roster.pending = roster.pending, nil;
92                 else
93                         metadata.pending = {};
94                 end
95         end
96         return metadata;
97 end
98
99 function load_roster(username, host)
100         local jid = username.."@"..host;
101         log("debug", "load_roster: asked for: %s", jid);
102         local user = bare_sessions[jid];
103         local roster;
104         if user then
105                 roster = user.roster;
106                 if roster then return roster; end
107                 log("debug", "load_roster: loading for new user: %s@%s", username, host);
108         else -- Attempt to load roster for non-loaded user
109                 log("debug", "load_roster: loading for offline user: %s@%s", username, host);
110         end
111         local data, err = datamanager.load(username, host, "roster");
112         roster = data or {};
113         if user then user.roster = roster; end
114         roster_metadata(roster, err);
115         if roster[jid] then
116                 roster[jid] = nil;
117                 log("warn", "roster for %s has a self-contact", jid);
118         end
119         if not err then
120                 hosts[host].events.fire_event("roster-load", { username = username, host = host, roster = roster });
121         end
122         return roster, err;
123 end
124
125 function save_roster(username, host, roster)
126         if not um_user_exists(username, host) then
127                 log("debug", "not saving roster for %s@%s: the user doesn't exist", username, host);
128                 return nil;
129         end
130
131         log("debug", "save_roster: saving roster for %s@%s", username, host);
132         if not roster then
133                 roster = hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster;
134                 --if not roster then
135                 --      --roster = load_roster(username, host);
136                 --      return true; -- roster unchanged, no reason to save
137                 --end
138         end
139         if roster then
140                 local metadata = roster_metadata(roster);
141                 if metadata.version ~= true then
142                         metadata.version = (metadata.version or 0) + 1;
143                 end
144                 if metadata.broken then return nil, "Not saving broken roster" end
145                 return datamanager.store(username, host, "roster", roster);
146         end
147         log("warn", "save_roster: user had no roster to save");
148         return nil;
149 end
150
151 function process_inbound_subscription_approval(username, host, jid)
152         local roster = load_roster(username, host);
153         local item = roster[jid];
154         if item and item.ask then
155                 if item.subscription == "none" then
156                         item.subscription = "to";
157                 else -- subscription == from
158                         item.subscription = "both";
159                 end
160                 item.ask = nil;
161                 return save_roster(username, host, roster);
162         end
163 end
164
165 function process_inbound_subscription_cancellation(username, host, jid)
166         local roster = load_roster(username, host);
167         local item = roster[jid];
168         local changed = nil;
169         if is_contact_pending_out(username, host, jid) then
170                 item.ask = nil;
171                 changed = true;
172         end
173         if item then
174                 if item.subscription == "to" then
175                         item.subscription = "none";
176                         changed = true;
177                 elseif item.subscription == "both" then
178                         item.subscription = "from";
179                         changed = true;
180                 end
181         end
182         if changed then
183                 return save_roster(username, host, roster);
184         end
185 end
186
187 function process_inbound_unsubscribe(username, host, jid)
188         local roster = load_roster(username, host);
189         local item = roster[jid];
190         local changed = nil;
191         if is_contact_pending_in(username, host, jid) then
192                 roster[false].pending[jid] = nil;
193                 changed = true;
194         end
195         if item then
196                 if item.subscription == "from" then
197                         item.subscription = "none";
198                         changed = true;
199                 elseif item.subscription == "both" then
200                         item.subscription = "to";
201                         changed = true;
202                 end
203         end
204         if changed then
205                 return save_roster(username, host, roster);
206         end
207 end
208
209 local function _get_online_roster_subscription(jidA, jidB)
210         local user = bare_sessions[jidA];
211         local item = user and (user.roster[jidB] or { subscription = "none" });
212         return item and item.subscription;
213 end
214 function is_contact_subscribed(username, host, jid)
215         do
216                 local selfjid = username.."@"..host;
217                 local subscription = _get_online_roster_subscription(selfjid, jid);
218                 if subscription then return (subscription == "both" or subscription == "from"); end
219                 local subscription = _get_online_roster_subscription(jid, selfjid);
220                 if subscription then return (subscription == "both" or subscription == "to"); end
221         end
222         local roster, err = load_roster(username, host);
223         local item = roster[jid];
224         return item and (item.subscription == "from" or item.subscription == "both"), err;
225 end
226
227 function is_contact_pending_in(username, host, jid)
228         local roster = load_roster(username, host);
229         return roster[false].pending[jid];
230 end
231 function set_contact_pending_in(username, host, jid)
232         local roster = load_roster(username, host);
233         local item = roster[jid];
234         if item and (item.subscription == "from" or item.subscription == "both") then
235                 return; -- false
236         end
237         roster[false].pending[jid] = true;
238         return save_roster(username, host, roster);
239 end
240 function is_contact_pending_out(username, host, jid)
241         local roster = load_roster(username, host);
242         local item = roster[jid];
243         return item and item.ask;
244 end
245 function set_contact_pending_out(username, host, jid) -- subscribe
246         local roster = load_roster(username, host);
247         local item = roster[jid];
248         if item and (item.ask or item.subscription == "to" or item.subscription == "both") then
249                 return true;
250         end
251         if not item then
252                 item = {subscription = "none", groups = {}};
253                 roster[jid] = item;
254         end
255         item.ask = "subscribe";
256         log("debug", "set_contact_pending_out: saving roster; set %s@%s.roster[%q].ask=subscribe", username, host, jid);
257         return save_roster(username, host, roster);
258 end
259 function unsubscribe(username, host, jid)
260         local roster = load_roster(username, host);
261         local item = roster[jid];
262         if not item then return false; end
263         if (item.subscription == "from" or item.subscription == "none") and not item.ask then
264                 return true;
265         end
266         item.ask = nil;
267         if item.subscription == "both" then
268                 item.subscription = "from";
269         elseif item.subscription == "to" then
270                 item.subscription = "none";
271         end
272         return save_roster(username, host, roster);
273 end
274 function subscribed(username, host, jid)
275         if is_contact_pending_in(username, host, jid) then
276                 local roster = load_roster(username, host);
277                 local item = roster[jid];
278                 if not item then -- FIXME should roster item be auto-created?
279                         item = {subscription = "none", groups = {}};
280                         roster[jid] = item;
281                 end
282                 if item.subscription == "none" then
283                         item.subscription = "from";
284                 else -- subscription == to
285                         item.subscription = "both";
286                 end
287                 roster[false].pending[jid] = nil;
288                 return save_roster(username, host, roster);
289         end -- TODO else implement optional feature pre-approval (ask = subscribed)
290 end
291 function unsubscribed(username, host, jid)
292         local roster = load_roster(username, host);
293         local item = roster[jid];
294         local pending = is_contact_pending_in(username, host, jid);
295         if pending then
296                 roster[false].pending[jid] = nil;
297         end
298         local subscribed;
299         if item then
300                 if item.subscription == "from" then
301                         item.subscription = "none";
302                         subscribed = true;
303                 elseif item.subscription == "both" then
304                         item.subscription = "to";
305                         subscribed = true;
306                 end
307         end
308         local success = (pending or subscribed) and save_roster(username, host, roster);
309         return success, pending, subscribed;
310 end
311
312 function process_outbound_subscription_request(username, host, jid)
313         local roster = load_roster(username, host);
314         local item = roster[jid];
315         if item and (item.subscription == "none" or item.subscription == "from") then
316                 item.ask = "subscribe";
317                 return save_roster(username, host, roster);
318         end
319 end
320
321 --[[function process_outbound_subscription_approval(username, host, jid)
322         local roster = load_roster(username, host);
323         local item = roster[jid];
324         if item and (item.subscription == "none" or item.subscription == "from" then
325                 item.ask = "subscribe";
326                 return save_roster(username, host, roster);
327         end
328 end]]
329
330
331
332 return _M;