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.
8 -- luacheck: globals prosody.bare_sessions.?.roster
12 local log = require
"util.logger".init("rostermanager");
14 local new_id
= require
"util.id".short
;
15 local new_cache
= require
"util.cache".new
;
18 local tostring = tostring;
21 local hosts
= prosody
.hosts
;
22 local bare_sessions
= prosody
.bare_sessions
;
24 local um_user_exists
= require
"core.usermanager".user_exists
;
25 local st
= require
"util.stanza";
26 local storagemanager
= require
"core.storagemanager";
31 local save_roster
; -- forward declaration
33 local function add_to_roster(session
, jid
, item
)
34 if session
.roster
then
35 local old_item
= session
.roster
[jid
];
36 session
.roster
[jid
] = item
;
37 if save_roster(session
.username
, session
.host
, nil, jid
) then
40 session
.roster
[jid
] = old_item
;
41 return nil, "wait", "internal-server-error", "Unable to save roster";
44 return nil, "auth", "not-authorized", "Session's roster not loaded";
48 local function remove_from_roster(session
, jid
)
49 if session
.roster
then
50 local old_item
= session
.roster
[jid
];
51 session
.roster
[jid
] = nil;
52 if save_roster(session
.username
, session
.host
, nil, jid
) then
55 session
.roster
[jid
] = old_item
;
56 return nil, "wait", "internal-server-error", "Unable to save roster";
59 return nil, "auth", "not-authorized", "Session's roster not loaded";
63 local function roster_push(username
, host
, jid
)
64 local roster
= jid
and hosts
[host
] and hosts
[host
].sessions
[username
] and hosts
[host
].sessions
[username
].roster
;
66 local item
= hosts
[host
].sessions
[username
].roster
[jid
];
67 local stanza
= st
.iq({type="set", id
=new_id()});
68 stanza
:tag("query", {xmlns
= "jabber:iq:roster", ver
= tostring(roster
[false].version
or "1") });
70 stanza
:tag("item", {jid
= jid
, subscription
= item
.subscription
, name
= item
.name
, ask
= item
.ask
});
71 for group
in pairs(item
.groups
) do
72 stanza
:tag("group"):text(group
):up();
75 stanza
:tag("item", {jid
= jid
, subscription
= "remove"});
77 stanza
:up(); -- move out from item
78 stanza
:up(); -- move out from stanza
80 for _
, session
in pairs(hosts
[host
].sessions
[username
].sessions
) do
81 if session
.interested
then
88 local function roster_metadata(roster
, err
)
89 local metadata
= roster
[false];
91 metadata
= { broken
= err
or nil };
92 roster
[false] = metadata
;
94 if roster
.pending
and type(roster
.pending
.subscription
) ~= "string" then
95 metadata
.pending
= roster
.pending
;
97 elseif not metadata
.pending
then
98 metadata
.pending
= {};
103 local function load_roster(username
, host
)
104 local jid
= username
.."@"..host
;
105 log("debug", "load_roster: asked for: %s", jid
);
106 local user
= bare_sessions
[jid
];
109 roster
= user
.roster
;
110 if roster
then return roster
; end
111 log("debug", "load_roster: loading for new user: %s", jid
);
112 else -- Attempt to load roster for non-loaded user
113 log("debug", "load_roster: loading for offline user: %s", jid
);
115 local roster_cache
= hosts
[host
] and hosts
[host
].roster_cache
;
116 if not roster_cache
then
118 roster_cache
= new_cache(1024);
119 hosts
[host
].roster_cache
= roster_cache
;
122 roster
= roster_cache
:get(jid
);
124 log("debug", "load_roster: cache hit");
125 roster_cache
:set(jid
, roster
);
126 if user
then user
.roster
= roster
; end
129 log("debug", "load_roster: cache miss, loading from storage");
132 local roster_store
= storagemanager
.open(host
, "roster", "keyval");
133 local data
, err
= roster_store
:get(username
);
135 if user
then user
.roster
= roster
; end
136 local legacy_pending
= roster
.pending
and type(roster
.pending
.subscription
) ~= "string";
137 roster_metadata(roster
, err
);
138 if legacy_pending
then
139 -- Due to map store use, we need to manually delete this entry
140 log("debug", "Removing legacy 'pending' entry");
141 if not save_roster(username
, host
, roster
, "pending") then
142 log("warn", "Could not remove legacy 'pendig' entry");
147 log("debug", "Roster for %s had a self-contact, removing", jid
);
148 if not save_roster(username
, host
, roster
, jid
) then
149 log("warn", "Could not remove self-contact from roster for %s", jid
);
153 hosts
[host
].events
.fire_event("roster-load", { username
= username
, host
= host
, roster
= roster
});
155 if roster_cache
and not user
then
156 log("debug", "load_roster: caching loaded roster");
157 roster_cache
:set(jid
, roster
);
162 function save_roster(username
, host
, roster
, jid
)
163 if not um_user_exists(username
, host
) then
164 log("debug", "not saving roster for %s@%s: the user doesn't exist", username
, host
);
168 log("debug", "save_roster: saving roster for %s@%s, (%s)", username
, host
, jid
or "all contacts");
170 roster
= hosts
[host
] and hosts
[host
].sessions
[username
] and hosts
[host
].sessions
[username
].roster
;
172 -- --roster = load_roster(username, host);
173 -- return true; -- roster unchanged, no reason to save
177 local metadata
= roster_metadata(roster
);
178 if metadata
.version
~= true then
179 metadata
.version
= (metadata
.version
or 0) + 1;
181 if metadata
.broken
then return nil, "Not saving broken roster" end
183 local roster_store
= storagemanager
.open(host
, "roster", "keyval");
184 return roster_store
:set(username
, roster
);
186 local roster_store
= storagemanager
.open(host
, "roster", "map");
187 return roster_store
:set_keys(username
, { [false] = metadata
, [jid
] = roster
[jid
] or roster_store
.remove });
190 log("warn", "save_roster: user had no roster to save");
194 local function process_inbound_subscription_approval(username
, host
, jid
)
195 local roster
= load_roster(username
, host
);
196 local item
= roster
[jid
];
197 if item
and item
.ask
then
198 if item
.subscription
== "none" then
199 item
.subscription
= "to";
200 else -- subscription == from
201 item
.subscription
= "both";
204 return save_roster(username
, host
, roster
, jid
);
208 local is_contact_pending_out
-- forward declaration
210 local function process_inbound_subscription_cancellation(username
, host
, jid
)
211 local roster
= load_roster(username
, host
);
212 local item
= roster
[jid
];
214 if is_contact_pending_out(username
, host
, jid
) then
219 if item
.subscription
== "to" then
220 item
.subscription
= "none";
222 elseif item
.subscription
== "both" then
223 item
.subscription
= "from";
228 return save_roster(username
, host
, roster
, jid
);
232 local is_contact_pending_in
-- forward declaration
234 local function process_inbound_unsubscribe(username
, host
, jid
)
235 local roster
= load_roster(username
, host
);
236 local item
= roster
[jid
];
238 if is_contact_pending_in(username
, host
, jid
) then
239 roster
[false].pending
[jid
] = nil;
243 if item
.subscription
== "from" then
244 item
.subscription
= "none";
246 elseif item
.subscription
== "both" then
247 item
.subscription
= "to";
252 return save_roster(username
, host
, roster
, jid
);
256 local function _get_online_roster_subscription(jidA
, jidB
)
257 local user
= bare_sessions
[jidA
];
258 local item
= user
and (user
.roster
[jidB
] or { subscription
= "none" });
259 return item
and item
.subscription
;
261 local function is_contact_subscribed(username
, host
, jid
)
263 local selfjid
= username
.."@"..host
;
264 local user_subscription
= _get_online_roster_subscription(selfjid
, jid
);
265 if user_subscription
then return (user_subscription
== "both" or user_subscription
== "from"); end
266 local contact_subscription
= _get_online_roster_subscription(jid
, selfjid
);
267 if contact_subscription
then return (contact_subscription
== "both" or contact_subscription
== "to"); end
269 local roster
, err
= load_roster(username
, host
);
270 local item
= roster
[jid
];
271 return item
and (item
.subscription
== "from" or item
.subscription
== "both"), err
;
273 local function is_user_subscribed(username
, host
, jid
)
275 local selfjid
= username
.."@"..host
;
276 local user_subscription
= _get_online_roster_subscription(selfjid
, jid
);
277 if user_subscription
then return (user_subscription
== "both" or user_subscription
== "to"); end
278 local contact_subscription
= _get_online_roster_subscription(jid
, selfjid
);
279 if contact_subscription
then return (contact_subscription
== "both" or contact_subscription
== "from"); end
281 local roster
, err
= load_roster(username
, host
);
282 local item
= roster
[jid
];
283 return item
and (item
.subscription
== "to" or item
.subscription
== "both"), err
;
286 function is_contact_pending_in(username
, host
, jid
)
287 local roster
= load_roster(username
, host
);
288 return roster
[false].pending
[jid
] ~= nil;
290 local function set_contact_pending_in(username
, host
, jid
, stanza
)
291 local roster
= load_roster(username
, host
);
292 local item
= roster
[jid
];
293 if item
and (item
.subscription
== "from" or item
.subscription
== "both") then
296 roster
[false].pending
[jid
] = st
.is_stanza(stanza
) and st
.preserialize(stanza
) or true;
297 return save_roster(username
, host
, roster
, jid
);
299 function is_contact_pending_out(username
, host
, jid
)
300 local roster
= load_roster(username
, host
);
301 local item
= roster
[jid
];
302 return item
and item
.ask
;
304 local function set_contact_pending_out(username
, host
, jid
) -- subscribe
305 local roster
= load_roster(username
, host
);
306 local item
= roster
[jid
];
307 if item
and (item
.ask
or item
.subscription
== "to" or item
.subscription
== "both") then
311 item
= {subscription
= "none", groups
= {}};
314 item
.ask
= "subscribe";
315 log("debug", "set_contact_pending_out: saving roster; set %s@%s.roster[%q].ask=subscribe", username
, host
, jid
);
316 return save_roster(username
, host
, roster
, jid
);
318 local function unsubscribe(username
, host
, jid
)
319 local roster
= load_roster(username
, host
);
320 local item
= roster
[jid
];
321 if not item
then return false; end
322 if (item
.subscription
== "from" or item
.subscription
== "none") and not item
.ask
then
326 if item
.subscription
== "both" then
327 item
.subscription
= "from";
328 elseif item
.subscription
== "to" then
329 item
.subscription
= "none";
331 return save_roster(username
, host
, roster
, jid
);
333 local function subscribed(username
, host
, jid
)
334 if is_contact_pending_in(username
, host
, jid
) then
335 local roster
= load_roster(username
, host
);
336 local item
= roster
[jid
];
337 if not item
then -- FIXME should roster item be auto-created?
338 item
= {subscription
= "none", groups
= {}};
341 if item
.subscription
== "none" then
342 item
.subscription
= "from";
343 else -- subscription == to
344 item
.subscription
= "both";
346 roster
[false].pending
[jid
] = nil;
347 return save_roster(username
, host
, roster
, jid
);
348 end -- TODO else implement optional feature pre-approval (ask = subscribed)
350 local function unsubscribed(username
, host
, jid
)
351 local roster
= load_roster(username
, host
);
352 local item
= roster
[jid
];
353 local pending
= is_contact_pending_in(username
, host
, jid
);
355 roster
[false].pending
[jid
] = nil;
359 if item
.subscription
== "from" then
360 item
.subscription
= "none";
361 is_subscribed
= true;
362 elseif item
.subscription
== "both" then
363 item
.subscription
= "to";
364 is_subscribed
= true;
367 local success
= (pending
or is_subscribed
) and save_roster(username
, host
, roster
, jid
);
368 return success
, pending
, is_subscribed
;
371 local function process_outbound_subscription_request(username
, host
, jid
)
372 local roster
= load_roster(username
, host
);
373 local item
= roster
[jid
];
374 if item
and (item
.subscription
== "none" or item
.subscription
== "from") then
375 item
.ask
= "subscribe";
376 return save_roster(username
, host
, roster
, jid
);
380 --[[function process_outbound_subscription_approval(username, host, jid)
381 local roster = load_roster(username, host);
382 local item = roster[jid];
383 if item and (item.subscription == "none" or item.subscription == "from" then
384 item.ask = "subscribe";
385 return save_roster(username, host, roster);
392 add_to_roster
= add_to_roster
;
393 remove_from_roster
= remove_from_roster
;
394 roster_push
= roster_push
;
395 load_roster
= load_roster
;
396 save_roster
= save_roster
;
397 process_inbound_subscription_approval
= process_inbound_subscription_approval
;
398 process_inbound_subscription_cancellation
= process_inbound_subscription_cancellation
;
399 process_inbound_unsubscribe
= process_inbound_unsubscribe
;
400 is_contact_subscribed
= is_contact_subscribed
;
401 is_user_subscribed
= is_user_subscribed
;
402 is_contact_pending_in
= is_contact_pending_in
;
403 set_contact_pending_in
= set_contact_pending_in
;
404 is_contact_pending_out
= is_contact_pending_out
;
405 set_contact_pending_out
= set_contact_pending_out
;
406 unsubscribe
= unsubscribe
;
407 subscribed
= subscribed
;
408 unsubscribed
= unsubscribed
;
409 process_outbound_subscription_request
= process_outbound_subscription_request
;