Merge with waqas
[prosody.git] / core / rostermanager.lua
1 -- Prosody IM
2 -- Copyright (C) 2008-2009 Matthew Wild
3 -- Copyright (C) 2008-2009 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
22 local datamanager = require "util.datamanager"
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 jid ~= "pending" 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 function load_roster(username, host)
84         log("debug", "load_roster: asked for: "..username.."@"..host);
85         local roster;
86         if hosts[host] and hosts[host].sessions[username] then
87                 roster = hosts[host].sessions[username].roster;
88                 if not roster then
89                         log("debug", "load_roster: loading for new user: "..username.."@"..host);
90                         roster = datamanager.load(username, host, "roster") or {};
91                         if not roster[false] then roster[false] = { }; end
92                         hosts[host].sessions[username].roster = roster;
93                         hosts[host].events.fire_event("roster-load", username, host, roster);
94                 end
95                 return roster;
96         end
97         
98         -- Attempt to load roster for non-loaded user
99         log("debug", "load_roster: loading for offline user: "..username.."@"..host);
100         roster = datamanager.load(username, host, "roster") or {};
101         hosts[host].events.fire_event("roster-load", username, host, roster);
102         return roster;
103 end
104
105 function save_roster(username, host)
106         log("debug", "save_roster: saving roster for "..username.."@"..host);
107         if hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster then
108                 local roster = hosts[host].sessions[username].roster;
109                 roster[false].version = (roster[false].version or 1) + 1;
110                 return datamanager.store(username, host, "roster", hosts[host].sessions[username].roster);
111         end
112         log("warn", "save_roster: user had no roster to save");
113         return nil;
114 end
115
116 function process_inbound_subscription_approval(username, host, jid)
117         local roster = load_roster(username, host);
118         local item = roster[jid];
119         if item and item.ask then
120                 if item.subscription == "none" then
121                         item.subscription = "to";
122                 else -- subscription == from
123                         item.subscription = "both";
124                 end
125                 item.ask = nil;
126                 return datamanager.store(username, host, "roster", roster);
127         end
128 end
129
130 function process_inbound_subscription_cancellation(username, host, jid)
131         local roster = load_roster(username, host);
132         local item = roster[jid];
133         local changed = nil;
134         if is_contact_pending_out(username, host, jid) then
135                 item.ask = nil;
136                 changed = true;
137         end
138         if item then
139                 if item.subscription == "to" then
140                         item.subscription = "none";
141                         changed = true;
142                 elseif item.subscription == "both" then
143                         item.subscription = "from";
144                         changed = true;
145                 end
146         end
147         if changed then
148                 return datamanager.store(username, host, "roster", roster);
149         end
150 end
151
152 function process_inbound_unsubscribe(username, host, jid)
153         local roster = load_roster(username, host);
154         local item = roster[jid];
155         local changed = nil;
156         if is_contact_pending_in(username, host, jid) then
157                 roster.pending[jid] = nil; -- TODO maybe delete roster.pending if empty?
158                 changed = true;
159         end
160         if item then
161                 if item.subscription == "from" then
162                         item.subscription = "none";
163                         changed = true;
164                 elseif item.subscription == "both" then
165                         item.subscription = "to";
166                         changed = true;
167                 end
168         end
169         if changed then
170                 return datamanager.store(username, host, "roster", roster);
171         end
172 end
173
174 function is_contact_subscribed(username, host, jid)
175         local roster = load_roster(username, host);
176         local item = roster[jid];
177         return item and (item.subscription == "from" or item.subscription == "both");
178 end
179
180 function is_contact_pending_in(username, host, jid)
181         local roster = load_roster(username, host);
182         return roster.pending and roster.pending[jid];
183 end
184 function set_contact_pending_in(username, host, jid, pending)
185         local roster = load_roster(username, host);
186         local item = roster[jid];
187         if item and (item.subscription == "from" or item.subscription == "both") then
188                 return; -- false
189         end
190         if not roster.pending then roster.pending = {}; end
191         roster.pending[jid] = true;
192         return datamanager.store(username, host, "roster", roster);
193 end
194 function is_contact_pending_out(username, host, jid)
195         local roster = load_roster(username, host);
196         local item = roster[jid];
197         return item and item.ask;
198 end
199 function set_contact_pending_out(username, host, jid) -- subscribe
200         local roster = load_roster(username, host);
201         local item = roster[jid];
202         if item and (item.ask or item.subscription == "to" or item.subscription == "both") then
203                 return true;
204         end
205         if not item then
206                 item = {subscription = "none", groups = {}};
207                 roster[jid] = item;
208         end
209         item.ask = "subscribe";
210         log("debug", "set_contact_pending_out: saving roster; set "..username.."@"..host..".roster["..jid.."].ask=subscribe");
211         return datamanager.store(username, host, "roster", roster);
212 end
213 function unsubscribe(username, host, jid)
214         local roster = load_roster(username, host);
215         local item = roster[jid];
216         if not item then return false; end
217         if (item.subscription == "from" or item.subscription == "none") and not item.ask then
218                 return true;
219         end
220         item.ask = nil;
221         if item.subscription == "both" then
222                 item.subscription = "from";
223         elseif item.subscription == "to" then
224                 item.subscription = "none";
225         end
226         return datamanager.store(username, host, "roster", roster);
227 end
228 function subscribed(username, host, jid)
229         if is_contact_pending_in(username, host, jid) then
230                 local roster = load_roster(username, host);
231                 local item = roster[jid];
232                 if not item then -- FIXME should roster item be auto-created?
233                         item = {subscription = "none", groups = {}};
234                         roster[jid] = item;
235                 end
236                 if item.subscription == "none" then
237                         item.subscription = "from";
238                 else -- subscription == to
239                         item.subscription = "both";
240                 end
241                 roster.pending[jid] = nil;
242                 -- TODO maybe remove roster.pending if empty
243                 return datamanager.store(username, host, "roster", roster);
244         end -- TODO else implement optional feature pre-approval (ask = subscribed)
245 end
246 function unsubscribed(username, host, jid)
247         local roster = load_roster(username, host);
248         local item = roster[jid];
249         local pending = is_contact_pending_in(username, host, jid);
250         local changed = nil;
251         if is_contact_pending_in(username, host, jid) then
252                 roster.pending[jid] = nil; -- TODO maybe delete roster.pending if empty?
253                 changed = true;
254         end
255         if item then
256                 if item.subscription == "from" then
257                         item.subscription = "none";
258                         changed = true;
259                 elseif item.subscription == "both" then
260                         item.subscription = "to";
261                         changed = true;
262                 end
263         end
264         if changed then
265                 return datamanager.store(username, host, "roster", roster);
266         end
267 end
268
269 function process_outbound_subscription_request(username, host, jid)
270         local roster = load_roster(username, host);
271         local item = roster[jid];
272         if item and (item.subscription == "none" or item.subscription == "from") then
273                 item.ask = "subscribe";
274                 return datamanager.store(username, host, "roster", roster);
275         end
276 end
277
278 --[[function process_outbound_subscription_approval(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 datamanager.store(username, host, "roster", roster);
284         end
285 end]]
286
287
288
289 return _M;