File

plugins/mod_carbons.lua @ 11763:e273ef869794

net.server: Pikc server_epoll as unconditional default Previously it would have gone for server_select if util.poll was for some reason not available, which should be never these days. And even if it was, best to flush it out by throwing loud errors so users notice. Then they can work around it by using select until we delete that one.
author Kim Alvefur <zash@zash.se>
date Fri, 03 Sep 2021 17:39:00 +0200
parent 11260:08b397c21805
child 11796:71c20650a0b4
line wrap: on
line source

-- XEP-0280: Message Carbons implementation for Prosody
-- Copyright (C) 2011-2016 Kim Alvefur
--
-- This file is MIT/X11 licensed.

local st = require "util.stanza";
local jid_bare = require "util.jid".bare;
local jid_resource = require "util.jid".resource;
local xmlns_carbons = "urn:xmpp:carbons:2";
local xmlns_forward = "urn:xmpp:forward:0";
local full_sessions, bare_sessions = prosody.full_sessions, prosody.bare_sessions;

local function is_bare(jid)
	return not jid_resource(jid);
end

local function toggle_carbons(event)
	local origin, stanza = event.origin, event.stanza;
	local state = stanza.tags[1].name;
	module:log("debug", "%s %sd carbons", origin.full_jid, state);
	origin.want_carbons = state == "enable" and stanza.tags[1].attr.xmlns;
	origin.send(st.reply(stanza));
	return true;
end
module:hook("iq-set/self/"..xmlns_carbons..":disable", toggle_carbons);
module:hook("iq-set/self/"..xmlns_carbons..":enable", toggle_carbons);

local function should_copy(stanza, c2s, user_bare) --> boolean, reason: string
	local st_type = stanza.attr.type or "normal";
	if stanza:get_child("private", xmlns_carbons) then
		return false, "private";
	end

	if stanza:get_child("no-copy", "urn:xmpp:hints") then
		return false, "hint";
	end

	if not c2s and stanza.attr.to ~= user_bare and stanza:get_child("x", "http://jabber.org/protocol/muc#user") then
		-- MUC PMs are normally sent to full JIDs
		return false, "muc-pm";
	end

	if st_type == "chat" then
		return true, "type";
	end

	if st_type == "normal" and stanza:get_child("body") then
		return true, "type";
	end

	-- Normal outgoing chat messages are sent to=bare JID. This clause should
	-- match the error bounces from those, which would have from=bare JID and
	-- be incoming (not c2s).
	if st_type == "error" and not c2s and is_bare(stanza.attr.from) then
		return true, "bounce";
	end

	if stanza:get_child(nil, "urn:xmpp:jingle-message:0") then
		-- XXX Experimental XEP
		return true, "jingle call";
	end

	for archived in stanza:childtags("stanza-id", "urn:xmpp:sid:0") do
		if archived and archived.attr.by == user_bare then
			return true, "archived";
		end
	end

	return false, "default";
end

local function message_handler(event, c2s)
	local origin, stanza = event.origin, event.stanza;
	local orig_type = stanza.attr.type or "normal";
	local orig_from = stanza.attr.from;
	local bare_from = jid_bare(orig_from);
	local orig_to = stanza.attr.to;
	local bare_to = jid_bare(orig_to);

	-- Stanza sent by a local client
	local bare_jid = bare_from; -- JID of the local user
	local target_session = origin;
	local top_priority = false;
	local user_sessions = bare_sessions[bare_from];

	-- Stanza about to be delivered to a local client
	if not c2s then
		bare_jid = bare_to;
		target_session = full_sessions[orig_to];
		user_sessions = bare_sessions[bare_jid];
		if not target_session and user_sessions then
			-- The top resources will already receive this message per normal routing rules,
			-- so we are going to skip them in order to avoid sending duplicated messages.
			local top_resources = user_sessions.top_resources;
			top_priority = top_resources and top_resources[1].priority
		end
	end

	if not user_sessions then
		module:log("debug", "Skip carbons for offline user");
		return -- No use in sending carbons to an offline user
	end

	local should, why = should_copy(stanza, c2s, bare_jid);

	if not should then
		module:log("debug", "Not copying stanza: %s (%s)", stanza:top_tag(), why);
		if why == "private" and not c2s then
			stanza:maptags(function(tag)
				if not ( tag.attr.xmlns == xmlns_carbons and tag.name == "private" ) then
					return tag;
				end
			end);
		end
		return;
	end

	local carbon;
	user_sessions = user_sessions and user_sessions.sessions;
	for _, session in pairs(user_sessions) do
		-- Carbons are sent to resources that have enabled it
		if session.want_carbons
		-- but not the resource that sent the message, or the one that it's directed to
		and session ~= target_session
		-- and isn't among the top resources that would receive the message per standard routing rules
		and (c2s or session.priority ~= top_priority) then
			if not carbon then
				-- Create the carbon copy and wrap it as per the Stanza Forwarding XEP
				local copy = st.clone(stanza);
				if c2s and not orig_to then
					stanza.attr.to = bare_from;
				end
				copy.attr.xmlns = "jabber:client";
				carbon = st.message{ from = bare_jid, type = orig_type, }
					:tag(c2s and "sent" or "received", { xmlns = xmlns_carbons })
						:tag("forwarded", { xmlns = xmlns_forward })
							:add_child(copy):reset();

			end

			carbon.attr.to = session.full_jid;
			module:log("debug", "Sending carbon to %s", session.full_jid);
			session.send(carbon);
		end
	end
end

local function c2s_message_handler(event)
	return message_handler(event, true)
end

-- Stanzas sent by local clients
module:hook("pre-message/host", c2s_message_handler, -0.5);
module:hook("pre-message/bare", c2s_message_handler, -0.5);
module:hook("pre-message/full", c2s_message_handler, -0.5);
-- Stanzas to local clients
module:hook("message/bare", message_handler, -0.5);
module:hook("message/full", message_handler, -0.5);

module:add_feature(xmlns_carbons);