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.
9 local get_children
= require
"core.hostmanager".get_children
;
10 local is_contact_subscribed
= require
"core.rostermanager".is_contact_subscribed
;
11 local jid_split
= require
"util.jid".split
;
12 local jid_bare
= require
"util.jid".bare
;
13 local st
= require
"util.stanza"
14 local calculate_hash
= require
"util.caps".calculate_hash
;
16 local disco_items
= module
:get_option_array("disco_items", {})
17 do -- validate disco_items
18 for _
, item
in ipairs(disco_items
) do
20 if type(item
) ~= "table" then
21 err
= "item is not a table";
22 elseif type(item
[1]) ~= "string" then
23 err
= "item jid is not a string";
24 elseif item
[2] and type(item
[2]) ~= "string" then
25 err
= "item name is not a string";
28 module
:log("error", "option disco_items is malformed: %s", err
);
29 disco_items
= {}; -- TODO clean up data instead of removing it?
35 if module
:get_host_type() == "local" then
36 module
:add_identity("server", "im", module
:get_option_string("name", "Prosody")); -- FIXME should be in the non-existing mod_router
38 module
:add_feature("http://jabber.org/protocol/disco#info");
39 module
:add_feature("http://jabber.org/protocol/disco#items");
41 -- Generate and cache disco result and caps hash
42 local _cached_server_disco_info
, _cached_server_caps_feature
, _cached_server_caps_hash
;
43 local function build_server_disco_info()
44 local query
= st
.stanza("query", { xmlns
= "http://jabber.org/protocol/disco#info" });
46 for _
,identity
in ipairs(module
:get_host_items("identity")) do
47 local identity_s
= identity
.category
.."\0"..identity
.type;
48 if not done
[identity_s
] then
49 query
:tag("identity", identity
):up();
50 done
[identity_s
] = true;
53 for _
,feature
in ipairs(module
:get_host_items("feature")) do
54 if not done
[feature
] then
55 query
:tag("feature", {var
=feature
}):up();
59 for _
,extension
in ipairs(module
:get_host_items("extension")) do
60 if not done
[extension
] then
61 query
:add_child(extension
);
62 done
[extension
] = true;
65 _cached_server_disco_info
= query
;
66 _cached_server_caps_hash
= calculate_hash(query
);
67 _cached_server_caps_feature
= st
.stanza("c", {
68 xmlns
= "http://jabber.org/protocol/caps";
70 node
= "http://prosody.im";
71 ver
= _cached_server_caps_hash
;
74 local function clear_disco_cache()
75 _cached_server_disco_info
, _cached_server_caps_feature
, _cached_server_caps_hash
= nil, nil, nil;
77 local function get_server_disco_info()
78 if not _cached_server_disco_info
then build_server_disco_info(); end
79 return _cached_server_disco_info
;
81 local function get_server_caps_feature()
82 if not _cached_server_caps_feature
then build_server_disco_info(); end
83 return _cached_server_caps_feature
;
85 local function get_server_caps_hash()
86 if not _cached_server_caps_hash
then build_server_disco_info(); end
87 return _cached_server_caps_hash
;
90 module
:hook("item-added/identity", clear_disco_cache
);
91 module
:hook("item-added/feature", clear_disco_cache
);
92 module
:hook("item-added/extension", clear_disco_cache
);
93 module
:hook("item-removed/identity", clear_disco_cache
);
94 module
:hook("item-removed/feature", clear_disco_cache
);
95 module
:hook("item-removed/extension", clear_disco_cache
);
97 -- Handle disco requests to the server
98 module
:hook("iq-get/host/http://jabber.org/protocol/disco#info:query", function(event
)
99 local origin
, stanza
= event
.origin
, event
.stanza
;
100 local node
= stanza
.tags
[1].attr
.node
;
101 if node
and node
~= "" and node
~= "http://prosody.im#"..get_server_caps_hash() then
102 local reply
= st
.reply(stanza
):tag('query', {xmlns
='http://jabber.org/protocol/disco#info', node
=node
});
103 local node_event
= { origin
= origin
, stanza
= stanza
, reply
= reply
, node
= node
, exists
= false};
104 local ret
= module
:fire_event("host-disco-info-node", node_event
);
105 if ret
~= nil then return ret
; end
106 if node_event
.exists
then
109 origin
.send(st
.error_reply(stanza
, "cancel", "item-not-found", "Node does not exist"));
113 local reply_query
= get_server_disco_info();
114 reply_query
.attr
.node
= node
;
115 local reply
= st
.reply(stanza
):add_child(reply_query
);
119 module
:hook("iq-get/host/http://jabber.org/protocol/disco#items:query", function(event
)
120 local origin
, stanza
= event
.origin
, event
.stanza
;
121 local node
= stanza
.tags
[1].attr
.node
;
122 if node
and node
~= "" then
123 local reply
= st
.reply(stanza
):tag('query', {xmlns
='http://jabber.org/protocol/disco#items', node
=node
});
124 local node_event
= { origin
= origin
, stanza
= stanza
, reply
= reply
, node
= node
, exists
= false};
125 local ret
= module
:fire_event("host-disco-items-node", node_event
);
126 if ret
~= nil then return ret
; end
127 if node_event
.exists
then
130 origin
.send(st
.error_reply(stanza
, "cancel", "item-not-found", "Node does not exist"));
134 local reply
= st
.reply(stanza
):query("http://jabber.org/protocol/disco#items");
135 local ret
= module
:fire_event("host-disco-items", { origin
= origin
, stanza
= stanza
, reply
= reply
});
136 if ret
~= nil then return ret
; end
137 for jid
, name
in pairs(get_children(module
.host
)) do
138 reply
:tag("item", {jid
= jid
, name
= name
~=true and name
or nil}):up();
140 for _
, item
in ipairs(disco_items
) do
141 reply
:tag("item", {jid
=item
[1], name
=item
[2]}):up();
147 -- Handle caps stream feature
148 module
:hook("stream-features", function (event
)
149 if event
.origin
.type == "c2s" or event
.origin
.type == "c2s_unbound" then
150 event
.features
:add_child(get_server_caps_feature());
154 -- Handle disco requests to user accounts
155 if module
:get_host_type() ~= "local" then return end -- skip for components
156 module
:hook("iq-get/bare/http://jabber.org/protocol/disco#info:query", function(event
)
157 local origin
, stanza
= event
.origin
, event
.stanza
;
158 local node
= stanza
.tags
[1].attr
.node
;
159 local username
= jid_split(stanza
.attr
.to
) or origin
.username
;
160 if not stanza
.attr
.to
or is_contact_subscribed(username
, module
.host
, jid_bare(stanza
.attr
.from
)) then
161 if node
and node
~= "" then
162 local reply
= st
.reply(stanza
):tag('query', {xmlns
='http://jabber.org/protocol/disco#info', node
=node
});
163 if not reply
.attr
.from
then reply
.attr
.from
= origin
.username
.."@"..origin
.host
; end -- COMPAT To satisfy Psi when querying own account
164 local node_event
= { origin
= origin
, stanza
= stanza
, reply
= reply
, node
= node
, exists
= false};
165 local ret
= module
:fire_event("account-disco-info-node", node_event
);
166 if ret
~= nil then return ret
; end
167 if node_event
.exists
then
170 origin
.send(st
.error_reply(stanza
, "cancel", "item-not-found", "Node does not exist"));
174 local reply
= st
.reply(stanza
):tag('query', {xmlns
='http://jabber.org/protocol/disco#info'});
175 if not reply
.attr
.from
then reply
.attr
.from
= origin
.username
.."@"..origin
.host
; end -- COMPAT To satisfy Psi when querying own account
176 reply
:tag('identity', {category
='account', type='registered'}):up();
177 module
:fire_event("account-disco-info", { origin
= origin
, reply
= reply
});
182 module
:hook("iq-get/bare/http://jabber.org/protocol/disco#items:query", function(event
)
183 local origin
, stanza
= event
.origin
, event
.stanza
;
184 local node
= stanza
.tags
[1].attr
.node
;
185 local username
= jid_split(stanza
.attr
.to
) or origin
.username
;
186 if not stanza
.attr
.to
or is_contact_subscribed(username
, module
.host
, jid_bare(stanza
.attr
.from
)) then
187 if node
and node
~= "" then
188 local reply
= st
.reply(stanza
):tag('query', {xmlns
='http://jabber.org/protocol/disco#items', node
=node
});
189 if not reply
.attr
.from
then reply
.attr
.from
= origin
.username
.."@"..origin
.host
; end -- COMPAT To satisfy Psi when querying own account
190 local node_event
= { origin
= origin
, stanza
= stanza
, reply
= reply
, node
= node
, exists
= false};
191 local ret
= module
:fire_event("account-disco-items-node", node_event
);
192 if ret
~= nil then return ret
; end
193 if node_event
.exists
then
196 origin
.send(st
.error_reply(stanza
, "cancel", "item-not-found", "Node does not exist"));
200 local reply
= st
.reply(stanza
):tag('query', {xmlns
='http://jabber.org/protocol/disco#items'});
201 if not reply
.attr
.from
then reply
.attr
.from
= origin
.username
.."@"..origin
.host
; end -- COMPAT To satisfy Psi when querying own account
202 module
:fire_event("account-disco-items", { origin
= origin
, stanza
= stanza
, reply
= reply
});