cc1dad8df7c37b3a6356c961e815d635635b609e
[prosody.git] / core / rostermanager.lua
1 -- Prosody IM v0.3
2 -- Copyright (C) 2008 Matthew Wild
3 -- Copyright (C) 2008 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
19 local hosts = hosts;
20
21 local datamanager = require "util.datamanager"
22 local st = require "util.stanza";
23
24 module "rostermanager"
25
26 function add_to_roster(session, jid, item)
27         if session.roster then
28                 local old_item = session.roster[jid];
29                 session.roster[jid] = item;
30                 if save_roster(session.username, session.host) then
31                         return true;
32                 else
33                         session.roster[jid] = old_item;
34                         return nil, "wait", "internal-server-error", "Unable to save roster";
35                 end
36         else
37                 return nil, "auth", "not-authorized", "Session's roster not loaded";
38         end
39 end
40
41 function remove_from_roster(session, jid)
42         if session.roster then
43                 local old_item = session.roster[jid];
44                 session.roster[jid] = nil;
45                 if save_roster(session.username, session.host) then
46                         return true;
47                 else
48                         session.roster[jid] = old_item;
49                         return nil, "wait", "internal-server-error", "Unable to save roster";
50                 end
51         else
52                 return nil, "auth", "not-authorized", "Session's roster not loaded";
53         end
54 end
55
56 function roster_push(username, host, jid)
57         if jid ~= "pending" and hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster then
58                 local item = hosts[host].sessions[username].roster[jid];
59                 local stanza = st.iq({type="set"});
60                 stanza:tag("query", {xmlns = "jabber:iq:roster"});
61                 if item then
62                         stanza:tag("item", {jid = jid, subscription = item.subscription, name = item.name, ask = item.ask});
63                         for group in pairs(item.groups) do
64                                 stanza:tag("group"):text(group):up();
65                         end
66                 else
67                         stanza:tag("item", {jid = jid, subscription = "remove"});
68                 end
69                 stanza:up(); -- move out from item
70                 stanza:up(); -- move out from stanza
71                 -- stanza ready
72                 for _, session in pairs(hosts[host].sessions[username].sessions) do
73                         if session.interested then
74                                 -- FIXME do we need to set stanza.attr.to?
75                                 session.send(stanza);
76                         end
77                 end
78         end
79 end
80
81 function load_roster(username, host)
82         log("debug", "load_roster: asked for: "..username.."@"..host);
83         if hosts[host] and hosts[host].sessions[username] then
84                 local roster = hosts[host].sessions[username].roster;
85                 if not roster then
86                         log("debug", "load_roster: loading for new user: "..username.."@"..host);
87                         roster = datamanager.load(username, host, "roster") or {};
88                         hosts[host].sessions[username].roster = roster;
89                 end
90                 return roster;
91         end
92         -- Attempt to load roster for non-loaded user
93         log("debug", "load_roster: loading for offline user: "..username.."@"..host);
94         return datamanager.load(username, host, "roster") or {};
95 end
96
97 function save_roster(username, host)
98         log("debug", "save_roster: saving roster for "..username.."@"..host);
99         if hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster then
100                 return datamanager.store(username, host, "roster", hosts[host].sessions[username].roster);
101         end
102         return nil;
103 end
104
105 function process_inbound_subscription_approval(username, host, jid)
106         local roster = load_roster(username, host);
107         local item = roster[jid];
108         if item and item.ask then
109                 if item.subscription == "none" then
110                         item.subscription = "to";
111                 else -- subscription == from
112                         item.subscription = "both";
113                 end
114                 item.ask = nil;
115                 return datamanager.store(username, host, "roster", roster);
116         end
117 end
118
119 function process_inbound_subscription_cancellation(username, host, jid)
120         local roster = load_roster(username, host);
121         local item = roster[jid];
122         local changed = nil;
123         if is_contact_pending_out(username, host, jid) then
124                 item.ask = nil;
125                 changed = true;
126         end
127         if item then
128                 if item.subscription == "to" then
129                         item.subscription = "none";
130                         changed = true;
131                 elseif item.subscription == "both" then
132                         item.subscription = "from";
133                         changed = true;
134                 end
135         end
136         if changed then
137                 return datamanager.store(username, host, "roster", roster);
138         end
139 end
140
141 function process_inbound_unsubscribe(username, host, jid)
142         local roster = load_roster(username, host);
143         local item = roster[jid];
144         local changed = nil;
145         if is_contact_pending_in(username, host, jid) then
146                 roster.pending[jid] = nil; -- TODO maybe delete roster.pending if empty?
147                 changed = true;
148         end
149         if item then
150                 if item.subscription == "from" then
151                         item.subscription = "none";
152                         changed = true;
153                 elseif item.subscription == "both" then
154                         item.subscription = "to";
155                         changed = true;
156                 end
157         end
158         if changed then
159                 return datamanager.store(username, host, "roster", roster);
160         end
161 end
162
163 function is_contact_subscribed(username, host, jid)
164         local roster = load_roster(username, host);
165         local item = roster[jid];
166         return item and (item.subscription == "from" or item.subscription == "both");
167 end
168
169 function is_contact_pending_in(username, host, jid)
170         local roster = load_roster(username, host);
171         return roster.pending and roster.pending[jid];
172 end
173 function set_contact_pending_in(username, host, jid, pending)
174         local roster = load_roster(username, host);
175         local item = roster[jid];
176         if item and (item.subscription == "from" or item.subscription == "both") then
177                 return; -- false
178         end
179         if not roster.pending then roster.pending = {}; end
180         roster.pending[jid] = true;
181         return datamanager.store(username, host, "roster", roster);
182 end
183 function is_contact_pending_out(username, host, jid)
184         local roster = load_roster(username, host);
185         local item = roster[jid];
186         return item and item.ask;
187 end
188 function set_contact_pending_out(username, host, jid) -- subscribe
189         local roster = load_roster(username, host);
190         local item = roster[jid];
191         if item and (item.ask or item.subscription == "to" or item.subscription == "both") then
192                 return true;
193         end
194         if not item then
195                 item = {subscription = "none", groups = {}};
196                 roster[jid] = item;
197         end
198         item.ask = "subscribe";
199         log("debug", "set_contact_pending_out: saving roster; set "..username.."@"..host..".roster["..jid.."].ask=subscribe");
200         return datamanager.store(username, host, "roster", roster);
201 end
202 function unsubscribe(username, host, jid)
203         local roster = load_roster(username, host);
204         local item = roster[jid];
205         if not item then return false; end
206         if (item.subscription == "from" or item.subscription == "none") and not item.ask then
207                 return true;
208         end
209         item.ask = nil;
210         if item.subscription == "both" then
211                 item.subscription = "from";
212         elseif item.subscription == "to" then
213                 item.subscription = "none";
214         end
215         return datamanager.store(username, host, "roster", roster);
216 end
217 function subscribed(username, host, jid)
218         if is_contact_pending_in(username, host, jid) then
219                 local roster = load_roster(username, host);
220                 local item = roster[jid];
221                 if item.subscription == "none" then
222                         item.subscription = "from";
223                 else -- subscription == to
224                         item.subscription = "both";
225                 end
226                 roster.pending[jid] = nil;
227                 -- TODO maybe remove roster.pending if empty
228                 return datamanager.store(username, host, "roster", roster);
229         end -- TODO else implement optional feature pre-approval (ask = subscribed)
230 end
231 function unsubscribed(username, host, jid)
232         local roster = load_roster(username, host);
233         local item = roster[jid];
234         local pending = is_contact_pending_in(username, host, jid);
235         local changed = nil;
236         if is_contact_pending_in(username, host, jid) then
237                 roster.pending[jid] = nil; -- TODO maybe delete roster.pending if empty?
238                 changed = true;
239         end
240         if item then
241                 if item.subscription == "from" then
242                         item.subscription = "none";
243                         changed = true;
244                 elseif item.subscription == "both" then
245                         item.subscription = "to";
246                         changed = true;
247                 end
248         end
249         if changed then
250                 return datamanager.store(username, host, "roster", roster);
251         end
252 end
253
254 function process_outbound_subscription_request(username, host, jid)
255         local roster = load_roster(username, host);
256         local item = roster[jid];
257         if item and (item.subscription == "none" or item.subscription == "from") then
258                 item.ask = "subscribe";
259                 return datamanager.store(username, host, "roster", roster);
260         end
261 end
262
263 --[[function process_outbound_subscription_approval(username, host, jid)
264         local roster = load_roster(username, host);
265         local item = roster[jid];
266         if item and (item.subscription == "none" or item.subscription == "from" then
267                 item.ask = "subscribe";
268                 return datamanager.store(username, host, "roster", roster);
269         end
270 end]]
271
272
273
274 return _M;