"Fossies" - the Fresh Open Source Software Archive

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