"Fossies" - the Fresh Open Source Software Archive 
Member "jitsi-meet-7689/resources/prosody-plugins/util.lib.lua" (5 Dec 2023, 16156 Bytes) of package /linux/misc/jitsi-meet-7689.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Lua source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
1 local jid = require "util.jid";
2 local timer = require "util.timer";
3 local http = require "net.http";
4 local cache = require "util.cache";
5
6 local http_timeout = 30;
7 local have_async, async = pcall(require, "util.async");
8 local http_headers = {
9 ["User-Agent"] = "Prosody ("..prosody.version.."; "..prosody.platform..")"
10 };
11
12 local muc_domain_prefix = module:get_option_string("muc_mapper_domain_prefix", "conference");
13
14 -- defaults to module.host, the module that uses the utility
15 local muc_domain_base = module:get_option_string("muc_mapper_domain_base", module.host);
16
17 -- The "real" MUC domain that we are proxying to
18 local muc_domain = module:get_option_string("muc_mapper_domain", muc_domain_prefix.."."..muc_domain_base);
19
20 local escaped_muc_domain_base = muc_domain_base:gsub("%p", "%%%1");
21 local escaped_muc_domain_prefix = muc_domain_prefix:gsub("%p", "%%%1");
22 -- The pattern used to extract the target subdomain
23 -- (e.g. extract 'foo' from 'conference.foo.example.com')
24 local target_subdomain_pattern = "^"..escaped_muc_domain_prefix..".([^%.]+)%."..escaped_muc_domain_base;
25
26 -- table to store all incoming iqs without roomname in it, like discoinfo to the muc component
27 local roomless_iqs = {};
28
29 local split_subdomain_cache = cache.new(1000);
30 local extract_subdomain_cache = cache.new(1000);
31 local internal_room_jid_cache = cache.new(1000);
32
33 local moderated_subdomains = module:get_option_set("allowners_moderated_subdomains", {})
34 local moderated_rooms = module:get_option_set("allowners_moderated_rooms", {})
35
36 -- Utility function to split room JID to include room name and subdomain
37 -- (e.g. from room1@conference.foo.example.com/res returns (room1, example.com, res, foo))
38 local function room_jid_split_subdomain(room_jid)
39 local ret = split_subdomain_cache:get(room_jid);
40 if ret then
41 return ret.node, ret.host, ret.resource, ret.subdomain;
42 end
43
44 local node, host, resource = jid.split(room_jid);
45
46 local target_subdomain = host and host:match(target_subdomain_pattern);
47 local cache_value = {node=node, host=host, resource=resource, subdomain=target_subdomain};
48 split_subdomain_cache:set(room_jid, cache_value);
49 return node, host, resource, target_subdomain;
50 end
51
52 --- Utility function to check and convert a room JID from
53 --- virtual room1@conference.foo.example.com to real [foo]room1@conference.example.com
54 -- @param room_jid the room jid to match and rewrite if needed
55 -- @param stanza the stanza
56 -- @return returns room jid [foo]room1@conference.example.com when it has subdomain
57 -- otherwise room1@conference.example.com(the room_jid value untouched)
58 local function room_jid_match_rewrite(room_jid, stanza)
59 local node, _, resource, target_subdomain = room_jid_split_subdomain(room_jid);
60 if not target_subdomain then
61 -- module:log("debug", "No need to rewrite out 'to' %s", room_jid);
62 return room_jid;
63 end
64 -- Ok, rewrite room_jid address to new format
65 local new_node, new_host, new_resource;
66 if node then
67 new_node, new_host, new_resource = "["..target_subdomain.."]"..node, muc_domain, resource;
68 else
69 -- module:log("debug", "No room name provided so rewriting only host 'to' %s", room_jid);
70 new_host, new_resource = muc_domain, resource;
71
72 if (stanza and stanza.attr and stanza.attr.id) then
73 roomless_iqs[stanza.attr.id] = stanza.attr.to;
74 end
75 end
76
77 return jid.join(new_node, new_host, new_resource);
78 end
79
80 -- Utility function to check and convert a room JID from real [foo]room1@muc.example.com to virtual room1@muc.foo.example.com
81 local function internal_room_jid_match_rewrite(room_jid, stanza)
82 -- first check for roomless_iqs
83 if (stanza and stanza.attr and stanza.attr.id and roomless_iqs[stanza.attr.id]) then
84 local result = roomless_iqs[stanza.attr.id];
85 roomless_iqs[stanza.attr.id] = nil;
86 return result;
87 end
88
89 local ret = internal_room_jid_cache:get(room_jid);
90 if ret then
91 return ret;
92 end
93
94 local node, host, resource = jid.split(room_jid);
95 if host ~= muc_domain or not node then
96 -- module:log("debug", "No need to rewrite %s (not from the MUC host)", room_jid);
97 internal_room_jid_cache:set(room_jid, room_jid);
98 return room_jid;
99 end
100
101 local target_subdomain, target_node = extract_subdomain(node);
102 if not (target_node and target_subdomain) then
103 -- module:log("debug", "Not rewriting... unexpected node format: %s", node);
104 internal_room_jid_cache:set(room_jid, room_jid);
105 return room_jid;
106 end
107
108 -- Ok, rewrite room_jid address to pretty format
109 ret = jid.join(target_node, muc_domain_prefix..".".. target_subdomain.."."..muc_domain_base, resource);
110 internal_room_jid_cache:set(room_jid, ret);
111 return ret;
112 end
113
114 --- Finds and returns room by its jid
115 -- @param room_jid the room jid to search in the muc component
116 -- @return returns room if found or nil
117 function get_room_from_jid(room_jid)
118 local _, host = jid.split(room_jid);
119 local component = hosts[host];
120 if component then
121 local muc = component.modules.muc
122 if muc and rawget(muc,"rooms") then
123 -- We're running 0.9.x or 0.10 (old MUC API)
124 return muc.rooms[room_jid];
125 elseif muc and rawget(muc,"get_room_from_jid") then
126 -- We're running >0.10 (new MUC API)
127 return muc.get_room_from_jid(room_jid);
128 else
129 return
130 end
131 end
132 end
133
134 -- Returns the room if available, work and in multidomain mode
135 -- @param room_name the name of the room
136 -- @param group name of the group (optional)
137 -- @return returns room if found or nil
138 function get_room_by_name_and_subdomain(room_name, subdomain)
139 local room_address;
140
141 -- if there is a subdomain we are in multidomain mode and that subdomain is not our main host
142 if subdomain and subdomain ~= "" and subdomain ~= muc_domain_base then
143 room_address = jid.join("["..subdomain.."]"..room_name, muc_domain);
144 else
145 room_address = jid.join(room_name, muc_domain);
146 end
147
148 return get_room_from_jid(room_address);
149 end
150
151 function async_handler_wrapper(event, handler)
152 if not have_async then
153 module:log("error", "requires a version of Prosody with util.async");
154 return nil;
155 end
156
157 local runner = async.runner;
158
159 -- Grab a local response so that we can send the http response when
160 -- the handler is done.
161 local response = event.response;
162 local async_func = runner(
163 function (event)
164 local result = handler(event)
165
166 -- If there is a status code in the result from the
167 -- wrapped handler then add it to the response.
168 if tonumber(result.status_code) ~= nil then
169 response.status_code = result.status_code
170 end
171
172 -- If there are headers in the result from the
173 -- wrapped handler then add them to the response.
174 if result.headers ~= nil then
175 response.headers = result.headers
176 end
177
178 -- Send the response to the waiting http client with
179 -- or without the body from the wrapped handler.
180 if result.body ~= nil then
181 response:send(result.body)
182 else
183 response:send();
184 end
185 end
186 )
187 async_func:run(event)
188 -- return true to keep the client http connection open.
189 return true;
190 end
191
192 --- Updates presence stanza, by adding identity node
193 -- @param stanza the presence stanza
194 -- @param user the user to which presence we are updating identity
195 -- @param group the group of the user to which presence we are updating identity
196 -- @param creator_user the user who created the user which presence we
197 -- are updating (this is the poltergeist case, where a user creates
198 -- a poltergeist), optional.
199 -- @param creator_group the group of the user who created the user which
200 -- presence we are updating (this is the poltergeist case, where a user creates
201 -- a poltergeist), optional.
202 function update_presence_identity(
203 stanza, user, group, creator_user, creator_group)
204
205 -- First remove any 'identity' element if it already
206 -- exists, so it cannot be spoofed by a client
207 stanza:maptags(
208 function(tag)
209 for k, v in pairs(tag) do
210 if k == "name" and v == "identity" then
211 return nil
212 end
213 end
214 return tag
215 end
216 )
217
218 stanza:tag("identity"):tag("user");
219 for k, v in pairs(user) do
220 v = tostring(v)
221 stanza:tag(k):text(v):up();
222 end
223 stanza:up();
224
225 -- Add the group information if it is present
226 if group then
227 stanza:tag("group"):text(group):up();
228 end
229
230 -- Add the creator user information if it is present
231 if creator_user then
232 stanza:tag("creator_user");
233 for k, v in pairs(creator_user) do
234 stanza:tag(k):text(v):up();
235 end
236 stanza:up();
237
238 -- Add the creator group information if it is present
239 if creator_group then
240 stanza:tag("creator_group"):text(creator_group):up();
241 end
242 end
243
244 stanza:up(); -- Close identity tag
245 end
246
247 -- Utility function to check whether feature is present and enabled. Allow
248 -- a feature if there are features present in the session(coming from
249 -- the token) and the value of the feature is true.
250 -- If features is not present in the token we skip feature detection and allow
251 -- everything.
252 function is_feature_allowed(features, ft)
253 if (features == nil or features[ft] == "true" or features[ft] == true) then
254 return true;
255 else
256 return false;
257 end
258 end
259
260 --- Extracts the subdomain and room name from internal jid node [foo]room1
261 -- @return subdomain(optional, if extracted or nil), the room name
262 function extract_subdomain(room_node)
263 local ret = extract_subdomain_cache:get(room_node);
264 if ret then
265 return ret.subdomain, ret.room;
266 end
267
268 local subdomain, room_name = room_node:match("^%[([^%]]+)%](.+)$");
269 local cache_value = {subdomain=subdomain, room=room_name};
270 extract_subdomain_cache:set(room_node, cache_value);
271 return subdomain, room_name;
272 end
273
274 function starts_with(str, start)
275 return str:sub(1, #start) == start
276 end
277
278 function ends_with(str, ending)
279 return ending == "" or str:sub(-#ending) == ending
280 end
281
282 -- healthcheck rooms in jicofo starts with a string '__jicofo-health-check'
283 function is_healthcheck_room(room_jid)
284 return starts_with(room_jid, "__jicofo-health-check");
285 end
286
287 --- Utility function to make an http get request and
288 --- retry @param retry number of times
289 -- @param url endpoint to be called
290 -- @param retry nr of retries, if retry is
291 -- @param auth_token value to be passed as auth Bearer
292 -- nil there will be no retries
293 -- @returns result of the http call or nil if
294 -- the external call failed after the last retry
295 function http_get_with_retry(url, retry, auth_token)
296 local content, code, cache_for;
297 local timeout_occurred;
298 local wait, done = async.waiter();
299 local request_headers = http_headers or {}
300 if auth_token ~= nil then
301 request_headers['Authorization'] = 'Bearer ' .. auth_token
302 end
303
304 local function cb(content_, code_, response_, request_)
305 if timeout_occurred == nil then
306 code = code_;
307 if code == 200 or code == 204 then
308 module:log("debug", "External call was successful, content %s", content_);
309 content = content_;
310
311 -- if there is cache-control header, let's return the max-age value
312 if response_ and response_.headers and response_.headers['cache-control'] then
313 local vals = {};
314 for k, v in response_.headers['cache-control']:gmatch('(%w+)=(%w+)') do
315 vals[k] = v;
316 end
317 -- max-age=123 will be parsed by the regex ^ to age=123
318 cache_for = vals.age;
319 end
320 else
321 module:log("warn", "Error on GET request: Code %s, Content %s",
322 code_, content_);
323 end
324 done();
325 else
326 module:log("warn", "External call reply delivered after timeout from: %s", url);
327 end
328 end
329
330 local function call_http()
331 return http.request(url, {
332 headers = request_headers,
333 method = "GET"
334 }, cb);
335 end
336
337 local request = call_http();
338
339 local function cancel()
340 -- TODO: This check is racey. Not likely to be a problem, but we should
341 -- still stick a mutex on content / code at some point.
342 if code == nil then
343 timeout_occurred = true;
344 module:log("warn", "Timeout %s seconds making the external call to: %s", http_timeout, url);
345 -- no longer present in prosody 0.11, so check before calling
346 if http.destroy_request ~= nil then
347 http.destroy_request(request);
348 end
349 if retry == nil then
350 module:log("debug", "External call failed and retry policy is not set");
351 done();
352 elseif retry ~= nil and retry < 1 then
353 module:log("debug", "External call failed after retry")
354 done();
355 else
356 module:log("debug", "External call failed, retry nr %s", retry)
357 retry = retry - 1;
358 request = call_http()
359 return http_timeout;
360 end
361 end
362 end
363 timer.add_task(http_timeout, cancel);
364 wait();
365
366 return content, code, cache_for;
367 end
368
369 -- Checks whether there is status in the <x node
370 -- @param muc_x the <x element from presence
371 -- @param status checks for this status
372 -- @returns true if the status is found, false otherwise or if no muc_x is provided.
373 function presence_check_status(muc_x, status)
374 if not muc_x then
375 return false;
376 end
377
378 for statusNode in muc_x:childtags('status') do
379 if statusNode.attr.code == status then
380 return true;
381 end
382 end
383
384 return false;
385 end
386
387 -- Retrieves the focus from the room and cache it in the room object
388 -- @param room The room name for which to find the occupant
389 local function get_focus_occupant(room)
390 return room:get_occupant_by_nick(room.jid..'/focus');
391 end
392
393 -- Checks whether the jid is moderated, the room name is in moderated_rooms
394 -- or if the subdomain is in the moderated_subdomains
395 -- @return returns on of the:
396 -- -> false
397 -- -> true, room_name, subdomain
398 -- -> true, room_name, nil (if no subdomain is used for the room)
399 function is_moderated(room_jid)
400 if moderated_subdomains:empty() and moderated_rooms:empty() then
401 return false;
402 end
403
404 local room_node = jid.node(room_jid);
405 -- parses bare room address, for multidomain expected format is:
406 -- [subdomain]roomName@conference.domain
407 local target_subdomain, target_room_name = extract_subdomain(room_node);
408 if target_subdomain then
409 if moderated_subdomains:contains(target_subdomain) then
410 return true, target_room_name, target_subdomain;
411 end
412 elseif moderated_rooms:contains(room_node) then
413 return true, room_node, nil;
414 end
415
416 return false;
417 end
418
419 return {
420 extract_subdomain = extract_subdomain;
421 is_feature_allowed = is_feature_allowed;
422 is_healthcheck_room = is_healthcheck_room;
423 is_moderated = is_moderated;
424 get_focus_occupant = get_focus_occupant;
425 get_room_from_jid = get_room_from_jid;
426 get_room_by_name_and_subdomain = get_room_by_name_and_subdomain;
427 async_handler_wrapper = async_handler_wrapper;
428 presence_check_status = presence_check_status;
429 room_jid_match_rewrite = room_jid_match_rewrite;
430 room_jid_split_subdomain = room_jid_split_subdomain;
431 internal_room_jid_match_rewrite = internal_room_jid_match_rewrite;
432 update_presence_identity = update_presence_identity;
433 http_get_with_retry = http_get_with_retry;
434 ends_with = ends_with;
435 starts_with = starts_with;
436 };