xmlhandlers: Don't restrict CDATA
[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 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;
19
20 local hosts = hosts;
21 local bare_sessions = bare_sessions;
22
23 local datamanager = require "util.datamanager"
24 local st = require "util.stanza";
25
26 module "rostermanager"
27
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
33                         return true;
34                 else
35                         session.roster[jid] = old_item;
36                         return nil, "wait", "internal-server-error", "Unable to save roster";
37                 end
38         else
39                 return nil, "auth", "not-authorized", "Session's roster not loaded";
40         end
41 end
42
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
48                         return true;
49                 else
50                         session.roster[jid] = old_item;
51                         return nil, "wait", "internal-server-error", "Unable to save roster";
52                 end
53         else
54                 return nil, "auth", "not-authorized", "Session's roster not loaded";
55         end
56 end
57
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;
60         if roster then
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")  });
64                 if item then
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();
68                         end
69                 else
70                         stanza:tag("item", {jid = jid, subscription = "remove"});
71                 end
72                 stanza:up(); -- move out from item
73                 stanza:up(); -- move out from stanza
74                 -- stanza ready
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?
78                                 session.send(stanza);
79                         end
80                 end
81         end
82 end
83
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];
88         local roster;
89         if user then
90                 roster = user.roster;
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);
95         end
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
99         if roster[jid] then
100                 roster[jid] = nil;
101                 log("warn", "roster for "..jid.." has a self-contact");
102         end
103         hosts[host].events.fire_event("roster-load", username, host, roster);
104         return roster;
105 end
106
107 function save_roster(username, host, roster)
108         log("debug", "save_roster: saving roster for "..username.."@"..host);
109         if not roster then
110                 roster = hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster;
111                 --if not roster then
112                 --      --roster = load_roster(username, host);
113                 --      return true; -- roster unchanged, no reason to save
114                 --end
115         end
116         if roster then
117                 local metadata = roster[false];
118                 if not metadata then
119                         metadata = {};
120                         roster[false] = metadata;
121                 end
122                 if metadata.version ~= true then
123                         metadata.version = (metadata.version or 0) + 1;
124                 end
125                 return datamanager.store(username, host, "roster", roster);
126         end
127         log("warn", "save_roster: user had no roster to save");
128         return nil;
129 end
130
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";
139                 end
140                 item.ask = nil;
141                 return save_roster(username, host, roster);
142         end
143 end
144
145 function process_inbound_subscription_cancellation(username, host, jid)
146         local roster = load_roster(username, host);
147         local item = roster[jid];
148         local changed = nil;
149         if is_contact_pending_out(username, host, jid) then
150                 item.ask = nil;
151                 changed = true;
152         end
153         if item then
154                 if item.subscription == "to" then
155                         item.subscription = "none";
156                         changed = true;
157                 elseif item.subscription == "both" then
158                         item.subscription = "from";
159                         changed = true;
160                 end
161         end
162         if changed then
163                 return save_roster(username, host, roster);
164         end
165 end
166
167 function process_inbound_unsubscribe(username, host, jid)
168         local roster = load_roster(username, host);
169         local item = roster[jid];
170         local changed = nil;
171         if is_contact_pending_in(username, host, jid) then
172                 roster.pending[jid] = nil; -- TODO maybe delete roster.pending if empty?
173                 changed = true;
174         end
175         if item then
176                 if item.subscription == "from" then
177                         item.subscription = "none";
178                         changed = true;
179                 elseif item.subscription == "both" then
180                         item.subscription = "to";
181                         changed = true;
182                 end
183         end
184         if changed then
185                 return save_roster(username, host, roster);
186         end
187 end
188
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");
193 end
194
195 function is_contact_pending_in(username, host, jid)
196         local roster = load_roster(username, host);
197         return roster.pending and roster.pending[jid];
198 end
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
203                 return; -- false
204         end
205         if not roster.pending then roster.pending = {}; end
206         roster.pending[jid] = true;
207         return save_roster(username, host, roster);
208 end
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;
213 end
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
218                 return true;
219         end
220         if not item then
221                 item = {subscription = "none", groups = {}};
222                 roster[jid] = item;
223         end
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);
227 end
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
233                 return true;
234         end
235         item.ask = nil;
236         if item.subscription == "both" then
237                 item.subscription = "from";
238         elseif item.subscription == "to" then
239                 item.subscription = "none";
240         end
241         return save_roster(username, host, roster);
242 end
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 = {}};
249                         roster[jid] = item;
250                 end
251                 if item.subscription == "none" then
252                         item.subscription = "from";
253                 else -- subscription == to
254                         item.subscription = "both";
255                 end
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)
260 end
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);
265         local changed = nil;
266         if is_contact_pending_in(username, host, jid) then
267                 roster.pending[jid] = nil; -- TODO maybe delete roster.pending if empty?
268                 changed = true;
269         end
270         if item then
271                 if item.subscription == "from" then
272                         item.subscription = "none";
273                         changed = true;
274                 elseif item.subscription == "both" then
275                         item.subscription = "to";
276                         changed = true;
277                 end
278         end
279         if changed then
280                 return save_roster(username, host, roster);
281         end
282 end
283
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);
290         end
291 end
292
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);
299         end
300 end]]
301
302
303
304 return _M;