Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0bd4d1aade | |||
| d49aa429ca | |||
| 316a19d600 | |||
| c43037887a | |||
| 538e0d4844 | |||
| f3795e9d03 | |||
| 3df919244c | |||
| 67773c5174 | |||
| 61dee97738 | |||
| 6774418a7f | |||
| ad6fcc7865 | |||
| ca28faa51a | |||
| 5b730cdbf2 | |||
| 9ed0357760 | |||
| 06ce884aa8 | |||
| 3fc0eb4f5b | |||
| 3bfa683586 | |||
| 5be49cc0fa | |||
| 42c029d5f7 | |||
| a567abcfdf | |||
| 265c7b62c7 | |||
| 332567693c | |||
| de7dc4affa | |||
| 48c5ab59f1 | |||
| b2855d63a7 | |||
| 0282cf64a0 | |||
| e5cb9dad40 | |||
| ec819b4002 | |||
| acc162f4f4 | |||
| b8505f3e78 | |||
| 8a71e2e4f7 | |||
| a5284229cb | |||
| d0f36537fb | |||
| 3cf4fbc7b0 | |||
| fe4b1a492c | |||
| c3b4b4ce4f | |||
| 95244c3b6f | |||
| 89d91b609a | |||
| d28064518b | |||
| 7627575856 | |||
| 99444f2d0e | |||
| 4c0f87b2ff | |||
| 54363f8476 | |||
| 094f586811 | |||
| 45a3c7e0ce | |||
| e2652ce02f | |||
| df651d893e | |||
| a2e1f5c882 | |||
| 7f5796fe31 | |||
| 5f1191b9f5 | |||
| 0041a11c4a | |||
| e17a16a300 | |||
| 7b3d26992b | |||
| 9373ad20ca | |||
| b283cfa6f2 | |||
| a84771fd14 | |||
| 2bb6782bee | |||
| ae151927ae | |||
| dfbdffad44 | |||
| d71bc73271 | |||
| ea9c3fd8f7 | |||
| 63dba3fd64 | |||
| ffe02c46e4 |
+14
-14
@@ -169,6 +169,20 @@ listen:
|
||||
max_stanza_size: 65536
|
||||
shaper: c2s_shaper
|
||||
access: c2s
|
||||
##
|
||||
## Direct-TLS for C2S (XEP-0368). A good practice is to forward
|
||||
## traffic from port 443 to this port, possibly multiplexing it
|
||||
## with HTTP using e.g. sslh [https://wiki.xmpp.org/web/Tech_pages/XEP-0368],
|
||||
## so modern clients can bypass restrictive firewalls (in airports, hotels, etc.).
|
||||
##
|
||||
## -
|
||||
## port: 5223
|
||||
## ip: "::"
|
||||
## module: ejabberd_c2s
|
||||
## tls: true
|
||||
## max_stanza_size: 65536
|
||||
## shaper: c2s_shaper
|
||||
## access: c2s
|
||||
-
|
||||
port: 5269
|
||||
ip: "::"
|
||||
@@ -185,20 +199,6 @@ listen:
|
||||
web_admin: true
|
||||
## register: true
|
||||
captcha: true
|
||||
##
|
||||
## Direct-TLS for C2S (XEP-0368). A good practice is to forward
|
||||
## traffic from port 443 to this port, possibly multiplexing it
|
||||
## with HTTP using e.g. sslh [https://wiki.xmpp.org/web/Tech_pages/XEP-0368],
|
||||
## so modern clients can bypass restrictive firewalls (in airports, hotels, etc.).
|
||||
##
|
||||
## -
|
||||
## port: 5223
|
||||
## ip: "::"
|
||||
## module: ejabberd_c2s
|
||||
## tls: true
|
||||
## max_stanza_size: 65536
|
||||
## shaper: c2s_shaper
|
||||
## access: c2s
|
||||
|
||||
##
|
||||
## ejabberd_service: Interact with external components (transports, ...)
|
||||
|
||||
+9
-11
@@ -41,19 +41,17 @@ case $(id -un) in
|
||||
esac
|
||||
|
||||
# parse command line parameters
|
||||
for arg; do
|
||||
case $arg in
|
||||
-n|--node) ERLANG_NODE_ARG=$2; shift;;
|
||||
-s|--spool) SPOOL_DIR=$2; shift;;
|
||||
-l|--logs) LOGS_DIR=$2; shift;;
|
||||
-f|--config) EJABBERD_CONFIG_PATH=$2; shift;;
|
||||
-c|--ctl-config) EJABBERDCTL_CONFIG_PATH=$2; shift;;
|
||||
-d|--config-dir) ETC_DIR=$2; shift;;
|
||||
-t|--no-timeout) NO_TIMEOUT="--no-timeout";;
|
||||
--) :;;
|
||||
while [ $# -gt 0 ]; do
|
||||
case $1 in
|
||||
-n|--node) ERLANG_NODE_ARG=$2; shift 2;;
|
||||
-s|--spool) SPOOL_DIR=$2; shift 2;;
|
||||
-l|--logs) LOGS_DIR=$2; shift 2;;
|
||||
-f|--config) EJABBERD_CONFIG_PATH=$2; shift 2;;
|
||||
-c|--ctl-config) EJABBERDCTL_CONFIG_PATH=$2; shift 2;;
|
||||
-d|--config-dir) ETC_DIR=$2; shift 2;;
|
||||
-t|--no-timeout) NO_TIMEOUT="--no-timeout"; shift;;
|
||||
*) break;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
# define ejabberd variables if not already defined from the command line
|
||||
|
||||
@@ -46,6 +46,6 @@
|
||||
buf :: binary(),
|
||||
http_opts = [] :: list()}).
|
||||
|
||||
-type method() :: 'GET' | 'HEAD' | 'DELETE' | 'OPTIONS' | 'PUT' | 'POST' | 'TRACE'.
|
||||
-type method() :: 'GET' | 'HEAD' | 'DELETE' | 'OPTIONS' | 'PUT' | 'POST' | 'TRACE' | 'PATCH'.
|
||||
-type protocol() :: http | https.
|
||||
-type http_request() :: #request{}.
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
max_users = ?MAX_USERS_DEFAULT :: non_neg_integer() | none,
|
||||
logging = false :: boolean(),
|
||||
vcard = <<"">> :: binary(),
|
||||
vcard_xupdate = undefined :: undefined | external | binary(),
|
||||
captcha_whitelist = (?SETS):empty() :: ?TGB_SET,
|
||||
mam = false :: boolean(),
|
||||
pubsub = <<"">> :: binary()
|
||||
|
||||
@@ -3,7 +3,7 @@ defmodule Ejabberd.Mixfile do
|
||||
|
||||
def project do
|
||||
[app: :ejabberd,
|
||||
version: "18.3.0",
|
||||
version: "18.4.0",
|
||||
description: description(),
|
||||
elixir: "~> 1.4",
|
||||
elixirc_paths: ["lib"],
|
||||
|
||||
+8
-8
@@ -22,17 +22,17 @@
|
||||
{tag, {if_version_above, "17", "3.4.2", "3.2.1"}}}},
|
||||
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.11"}}},
|
||||
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.13"}}},
|
||||
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.21"}}},
|
||||
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.22"}}},
|
||||
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.11"}}},
|
||||
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.29"}}},
|
||||
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.1.20"}}},
|
||||
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.13"}}},
|
||||
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.30"}}},
|
||||
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.1.21"}}},
|
||||
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.14"}}},
|
||||
{jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.8"}}},
|
||||
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.2"}}},
|
||||
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.3"}}},
|
||||
{jose, ".*", {git, "https://github.com/potatosalad/erlang-jose", {tag, "1.8.4"}}},
|
||||
{eimp, ".*", {git, "https://github.com/processone/eimp", {tag, "1.0.3"}}},
|
||||
{if_var_true, stun, {stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.21"}}}},
|
||||
{if_var_true, sip, {esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.22"}}}},
|
||||
{eimp, ".*", {git, "https://github.com/processone/eimp", {tag, "1.0.5"}}},
|
||||
{if_var_true, stun, {stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.22"}}}},
|
||||
{if_var_true, sip, {esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.23"}}}},
|
||||
{if_var_true, mysql, {p1_mysql, ".*", {git, "https://github.com/processone/p1_mysql",
|
||||
{tag, "1.0.5"}}}},
|
||||
{if_var_true, pgsql, {p1_pgsql, ".*", {git, "https://github.com/processone/p1_pgsql",
|
||||
|
||||
+2
-2
@@ -259,8 +259,8 @@ CREATE TABLE pubsub_item (
|
||||
nodeid bigint REFERENCES pubsub_node(nodeid) ON DELETE CASCADE,
|
||||
itemid text NOT NULL,
|
||||
publisher text NOT NULL,
|
||||
creation text NOT NULL,
|
||||
modification text NOT NULL,
|
||||
creation varchar(32) NOT NULL,
|
||||
modification varchar(32) NOT NULL,
|
||||
payload text NOT NULL DEFAULT ''
|
||||
);
|
||||
CREATE INDEX i_pubsub_item_itemid ON pubsub_item (itemid);
|
||||
|
||||
+2
-2
@@ -236,8 +236,8 @@ CREATE TABLE pubsub_item (
|
||||
nodeid bigint REFERENCES pubsub_node(nodeid) ON DELETE CASCADE,
|
||||
itemid text NOT NULL,
|
||||
publisher text NOT NULL,
|
||||
creation text NOT NULL,
|
||||
modification text NOT NULL,
|
||||
creation varchar(32) NOT NULL,
|
||||
modification varchar(32) NOT NULL,
|
||||
payload text NOT NULL DEFAULT ''
|
||||
);
|
||||
CREATE INDEX i_pubsub_item_itemid ON pubsub_item (itemid);
|
||||
|
||||
+2
-2
@@ -220,8 +220,8 @@ CREATE TABLE [dbo].[pubsub_item] (
|
||||
[nodeid] [bigint] NULL,
|
||||
[itemid] [varchar] (255) NOT NULL,
|
||||
[publisher] [text] NOT NULL,
|
||||
[creation] [text] NOT NULL,
|
||||
[modification] [varchar] (255) NOT NULL,
|
||||
[creation] [varchar] (32) NOT NULL,
|
||||
[modification] [varchar] (32) NOT NULL,
|
||||
[payload] [text] NOT NULL DEFAULT ''
|
||||
) TEXTIMAGE_ON [PRIMARY];
|
||||
|
||||
|
||||
+2
-2
@@ -274,8 +274,8 @@ CREATE TABLE pubsub_item (
|
||||
nodeid bigint,
|
||||
itemid text NOT NULL,
|
||||
publisher text NOT NULL,
|
||||
creation text NOT NULL,
|
||||
modification text NOT NULL,
|
||||
creation varchar(32) NOT NULL,
|
||||
modification varchar(32) NOT NULL,
|
||||
payload text NOT NULL
|
||||
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
CREATE INDEX i_pubsub_item_itemid ON pubsub_item(itemid(36));
|
||||
|
||||
+2
-2
@@ -251,8 +251,8 @@ CREATE TABLE pubsub_item (
|
||||
nodeid bigint,
|
||||
itemid text NOT NULL,
|
||||
publisher text NOT NULL,
|
||||
creation text NOT NULL,
|
||||
modification text NOT NULL,
|
||||
creation varchar(32) NOT NULL,
|
||||
modification varchar(32) NOT NULL,
|
||||
payload text NOT NULL
|
||||
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
CREATE INDEX i_pubsub_item_itemid ON pubsub_item(itemid(36));
|
||||
|
||||
+2
-2
@@ -428,8 +428,8 @@ CREATE TABLE pubsub_item (
|
||||
nodeid bigint REFERENCES pubsub_node(nodeid) ON DELETE CASCADE,
|
||||
itemid text NOT NULL,
|
||||
publisher text NOT NULL,
|
||||
creation text NOT NULL,
|
||||
modification text NOT NULL,
|
||||
creation varchar(32) NOT NULL,
|
||||
modification varchar(32) NOT NULL,
|
||||
payload text NOT NULL DEFAULT ''
|
||||
);
|
||||
CREATE INDEX i_pubsub_item_itemid ON pubsub_item USING btree (itemid);
|
||||
|
||||
+2
-2
@@ -254,8 +254,8 @@ CREATE TABLE pubsub_item (
|
||||
nodeid bigint REFERENCES pubsub_node(nodeid) ON DELETE CASCADE,
|
||||
itemid text NOT NULL,
|
||||
publisher text NOT NULL,
|
||||
creation text NOT NULL,
|
||||
modification text NOT NULL,
|
||||
creation varchar(32) NOT NULL,
|
||||
modification varchar(32) NOT NULL,
|
||||
payload text NOT NULL DEFAULT ''
|
||||
);
|
||||
CREATE INDEX i_pubsub_item_itemid ON pubsub_item USING btree (itemid);
|
||||
|
||||
+8
-3
@@ -25,6 +25,7 @@
|
||||
|
||||
-module(ejabberd).
|
||||
-author('alexey@process-one.net').
|
||||
-compile({no_auto_import, [{halt, 0}]}).
|
||||
|
||||
-protocol({xep, 4, '2.9'}).
|
||||
-protocol({xep, 86, '1.0'}).
|
||||
@@ -36,7 +37,7 @@
|
||||
-protocol({xep, 243, '1.0'}).
|
||||
-protocol({xep, 270, '1.0'}).
|
||||
|
||||
-export([start/0, stop/0, start_app/1, start_app/2,
|
||||
-export([start/0, stop/0, halt/0, start_app/1, start_app/2,
|
||||
get_pid_file/0, check_app/1, module_name/1]).
|
||||
|
||||
-include("logger.hrl").
|
||||
@@ -49,6 +50,11 @@ stop() ->
|
||||
application:stop(ejabberd).
|
||||
%%ejabberd_cover:stop().
|
||||
|
||||
halt() ->
|
||||
application:stop(lager),
|
||||
application:stop(sasl),
|
||||
erlang:halt(1, [{flush, true}]).
|
||||
|
||||
%% @spec () -> false | string()
|
||||
get_pid_file() ->
|
||||
case os:getenv("EJABBERD_PID_PATH") of
|
||||
@@ -131,8 +137,7 @@ exit_or_halt(Reason, StartFlag) ->
|
||||
?CRITICAL_MSG(Reason, []),
|
||||
if StartFlag ->
|
||||
%% Wait for the critical message is written in the console/log
|
||||
timer:sleep(1000),
|
||||
halt(string:substr(lists:flatten(Reason), 1, 199));
|
||||
halt();
|
||||
true ->
|
||||
erlang:error(application_start_failed)
|
||||
end.
|
||||
|
||||
@@ -62,8 +62,7 @@ start(normal, _Args) ->
|
||||
{ok, SupPid};
|
||||
Err ->
|
||||
?CRITICAL_MSG("Failed to start ejabberd application: ~p", [Err]),
|
||||
timer:sleep(1000),
|
||||
halt("Refer to ejabberd log files to diagnose the problem")
|
||||
ejabberd:halt()
|
||||
end;
|
||||
start(_, _) ->
|
||||
{error, badarg}.
|
||||
|
||||
@@ -455,8 +455,7 @@ get_config_lines2(Fd, Data, CurrLine, [NextWanted | LNumbers], R) when is_list(D
|
||||
exit_or_halt(ExitText) ->
|
||||
case [Vsn || {ejabberd, _Desc, Vsn} <- application:which_applications()] of
|
||||
[] ->
|
||||
timer:sleep(1000),
|
||||
halt(string:substr(ExitText, 1, 199));
|
||||
ejabberd:halt();
|
||||
[_] ->
|
||||
exit(ExitText)
|
||||
end.
|
||||
|
||||
+3
-54
@@ -31,7 +31,7 @@
|
||||
|
||||
%% External exports
|
||||
-export([start/2, start_link/2, become_controller/1,
|
||||
socket_type/0, receive_headers/1, url_encode/1,
|
||||
socket_type/0, receive_headers/1,
|
||||
transform_listen_option/2, listen_opt_type/1]).
|
||||
|
||||
-export([init/2, opt_type/1]).
|
||||
@@ -679,7 +679,7 @@ url_decode_q_split(<<>>, Ack) ->
|
||||
path_decode(Path) -> path_decode(Path, <<>>).
|
||||
|
||||
path_decode(<<$%, Hi, Lo, Tail/binary>>, Acc) ->
|
||||
Hex = hex_to_integer([Hi, Lo]),
|
||||
Hex = list_to_integer([Hi, Lo], 16),
|
||||
if Hex == 0 -> exit(badurl);
|
||||
true -> ok
|
||||
end,
|
||||
@@ -716,22 +716,6 @@ expand_custom_headers(Headers) ->
|
||||
{K, misc:expand_keyword(<<"@VERSION@">>, V, ?VERSION)}
|
||||
end, Headers).
|
||||
|
||||
%% hex_to_integer
|
||||
|
||||
hex_to_integer(Hex) ->
|
||||
case catch list_to_integer(Hex, 16) of
|
||||
{'EXIT', _} -> old_hex_to_integer(Hex);
|
||||
X -> X
|
||||
end.
|
||||
|
||||
old_hex_to_integer(Hex) ->
|
||||
DEHEX = fun (H) when H >= $a, H =< $f -> H - $a + 10;
|
||||
(H) when H >= $A, H =< $F -> H - $A + 10;
|
||||
(H) when H >= $0, H =< $9 -> H - $0
|
||||
end,
|
||||
lists:foldl(fun (E, Acc) -> Acc * 16 + DEHEX(E) end, 0,
|
||||
Hex).
|
||||
|
||||
code_to_phrase(100) -> <<"Continue">>;
|
||||
code_to_phrase(101) -> <<"Switching Protocols ">>;
|
||||
code_to_phrase(200) -> <<"OK">>;
|
||||
@@ -802,7 +786,7 @@ parse_urlencoded(S) ->
|
||||
|
||||
parse_urlencoded(<<$%, Hi, Lo, Tail/binary>>, Last, Cur,
|
||||
State) ->
|
||||
Hex = hex_to_integer([Hi, Lo]),
|
||||
Hex = list_to_integer([Hi, Lo], 16),
|
||||
parse_urlencoded(Tail, Last, <<Cur/binary, Hex>>, State);
|
||||
parse_urlencoded(<<$&, Tail/binary>>, _Last, Cur, key) ->
|
||||
[{Cur, <<"">>} | parse_urlencoded(Tail,
|
||||
@@ -822,41 +806,6 @@ parse_urlencoded(<<>>, Last, Cur, _State) ->
|
||||
[{Last, Cur}];
|
||||
parse_urlencoded(undefined, _, _, _) -> [].
|
||||
|
||||
|
||||
url_encode(A) ->
|
||||
url_encode(A, <<>>).
|
||||
|
||||
url_encode(<<H:8, T/binary>>, Acc) when
|
||||
(H >= $a andalso H =< $z) orelse
|
||||
(H >= $A andalso H =< $Z) orelse
|
||||
(H >= $0 andalso H =< $9) orelse
|
||||
H == $_ orelse
|
||||
H == $. orelse
|
||||
H == $- orelse
|
||||
H == $/ orelse
|
||||
H == $: ->
|
||||
url_encode(T, <<Acc/binary, H>>);
|
||||
url_encode(<<H:8, T/binary>>, Acc) ->
|
||||
case integer_to_hex(H) of
|
||||
[X, Y] -> url_encode(T, <<Acc/binary, $%, X, Y>>);
|
||||
[X] -> url_encode(T, <<Acc/binary, $%, $0, X>>)
|
||||
end;
|
||||
url_encode(<<>>, Acc) ->
|
||||
Acc.
|
||||
|
||||
|
||||
integer_to_hex(I) ->
|
||||
case catch erlang:integer_to_list(I, 16) of
|
||||
{'EXIT', _} -> old_integer_to_hex(I);
|
||||
Int -> Int
|
||||
end.
|
||||
|
||||
old_integer_to_hex(I) when I < 10 -> integer_to_list(I);
|
||||
old_integer_to_hex(I) when I < 16 -> [I - 10 + $A];
|
||||
old_integer_to_hex(I) when I >= 16 ->
|
||||
N = trunc(I / 16),
|
||||
old_integer_to_hex(N) ++ old_integer_to_hex(I rem 16).
|
||||
|
||||
% The following code is mostly taken from yaws_ssl.erl
|
||||
|
||||
toupper(C) when C >= $a andalso C =< $z -> C - 32;
|
||||
|
||||
@@ -134,6 +134,7 @@ init([{#ws{ip = IP, http_opts = HOpts}, _} = WS]) ->
|
||||
({resume_timeout, _}) -> true;
|
||||
({max_resume_timeout, _}) -> true;
|
||||
({resend_on_timeout, _}) -> true;
|
||||
({access, _}) -> true;
|
||||
(_) -> false
|
||||
end, HOpts),
|
||||
Opts = ejabberd_c2s_config:get_c2s_limits() ++ SOpts,
|
||||
|
||||
@@ -172,6 +172,7 @@ config_reloaded() ->
|
||||
true -> init_cache();
|
||||
false -> delete_cache()
|
||||
end,
|
||||
fast_tls:clear_cache(),
|
||||
gen_server:call(?MODULE, config_reloaded, 60000).
|
||||
|
||||
opt_type(ca_path) ->
|
||||
|
||||
@@ -191,8 +191,14 @@ handle_authenticated_packet(Pkt0, #{ip := {IP, _}, lang := Lang} = State)
|
||||
From = xmpp:get_from(Pkt),
|
||||
case check_from(From, State) of
|
||||
true ->
|
||||
ejabberd_router:route(Pkt),
|
||||
State;
|
||||
{Pkt2, State2} = ejabberd_hooks:run_fold(component_send_packet, {Pkt, State}, []),
|
||||
case Pkt2 of
|
||||
drop ->
|
||||
ok;
|
||||
_ ->
|
||||
ejabberd_router:route(Pkt2)
|
||||
end,
|
||||
State2;
|
||||
false ->
|
||||
Txt = <<"Improper domain part of 'from' attribute">>,
|
||||
Err = xmpp:serr_invalid_from(Txt, Lang),
|
||||
|
||||
@@ -42,7 +42,6 @@
|
||||
%%-include("logger.hrl").
|
||||
|
||||
-define(CHECK_INTERVAL, timer:seconds(30)).
|
||||
-define(DISK_FULL_THRES, 0.99).
|
||||
|
||||
-record(state, {tref :: reference(),
|
||||
mref :: reference()}).
|
||||
@@ -67,8 +66,7 @@ start() ->
|
||||
application:set_env(os_mon, start_cpu_sup, false),
|
||||
application:set_env(os_mon, start_os_sup, false),
|
||||
application:set_env(os_mon, start_memsup, true),
|
||||
application:set_env(os_mon, start_disksup, true),
|
||||
application:set_env(os_mon, disk_almost_full_threshold, ?DISK_FULL_THRES),
|
||||
application:set_env(os_mon, start_disksup, false),
|
||||
ejabberd:start_app(os_mon).
|
||||
|
||||
excluded_apps() ->
|
||||
@@ -81,16 +79,10 @@ init([]) ->
|
||||
{ok, #state{}}.
|
||||
|
||||
handle_event({set_alarm, {system_memory_high_watermark, _}}, State) ->
|
||||
error_logger:warning_msg(
|
||||
"More than 80% of OS memory is allocated, "
|
||||
"starting OOM watchdog", []),
|
||||
handle_overload(State),
|
||||
{ok, restart_timer(State)};
|
||||
handle_event({clear_alarm, system_memory_high_watermark}, State) ->
|
||||
cancel_timer(State#state.tref),
|
||||
error_logger:info_msg(
|
||||
"Memory consumption is back to normal, "
|
||||
"stopping OOM watchdog", []),
|
||||
{ok, State#state{tref = undefined}};
|
||||
handle_event({set_alarm, {process_memory_high_watermark, Pid}}, State) ->
|
||||
case proc_stat(Pid, get_app_pids()) of
|
||||
@@ -105,12 +97,6 @@ handle_event({set_alarm, {process_memory_high_watermark, Pid}}, State) ->
|
||||
end;
|
||||
handle_event({clear_alarm, process_memory_high_watermark}, State) ->
|
||||
{ok, State};
|
||||
handle_event({set_alarm, {{disk_almost_full, MountPoint}, _}}, State) ->
|
||||
error_logger:warning_msg("Disk is almost full on ~p", [MountPoint]),
|
||||
{ok, State};
|
||||
handle_event({clear_alarm, {disk_almost_full, MountPoint}}, State) ->
|
||||
error_logger:info_msg("Disk usage is back to normal on ~p", [MountPoint]),
|
||||
{ok, State};
|
||||
handle_event(Event, State) ->
|
||||
error_logger:warning_msg("unexpected event: ~p", [Event]),
|
||||
{ok, State}.
|
||||
|
||||
@@ -1240,7 +1240,7 @@ list_given_users(Host, Users, Prefix, Lang, URLFunc) ->
|
||||
?XE(<<"tr">>,
|
||||
[?XE(<<"td">>,
|
||||
[?AC((URLFunc({user, Prefix,
|
||||
ejabberd_http:url_encode(User),
|
||||
misc:url_encode(User),
|
||||
Server})),
|
||||
(us_to_list(US)))]),
|
||||
?XE(<<"td">>, FQueueLen),
|
||||
@@ -1319,7 +1319,7 @@ list_online_users(Host, _Lang) ->
|
||||
SUsers = lists:usort(Users),
|
||||
lists:flatmap(fun ({_S, U} = SU) ->
|
||||
[?AC(<<"../user/",
|
||||
(ejabberd_http:url_encode(U))/binary, "/">>,
|
||||
(misc:url_encode(U))/binary, "/">>,
|
||||
(su_to_list(SU))),
|
||||
?BR]
|
||||
end,
|
||||
|
||||
+13
-13
@@ -56,7 +56,8 @@ init([]) ->
|
||||
process_flag(trap_exit, true),
|
||||
[code:add_patha(module_ebin_dir(Module))
|
||||
|| {Module, _} <- installed()],
|
||||
p1_http:start(),
|
||||
application:start(inets),
|
||||
inets:start(httpc, [{profile, ext_mod}]),
|
||||
ejabberd_commands:register_commands(get_commands_spec()),
|
||||
{ok, #state{}}.
|
||||
|
||||
@@ -313,23 +314,22 @@ check(Package) when is_binary(Package) ->
|
||||
%% -- archives and variables functions
|
||||
|
||||
geturl(Url) ->
|
||||
geturl(Url, []).
|
||||
geturl(Url, UsrOpts) ->
|
||||
geturl(Url, [], UsrOpts).
|
||||
geturl(Url, Hdrs, UsrOpts) ->
|
||||
Host = case getenv("PROXY_SERVER", "", ":") of
|
||||
[H, Port] -> [{proxy_host, H}, {proxy_port, list_to_integer(Port)}];
|
||||
[H] -> [{proxy_host, H}, {proxy_port, 8080}];
|
||||
_ -> []
|
||||
case getenv("PROXY_SERVER", "", ":") of
|
||||
[H, Port] ->
|
||||
httpc:set_options([{proxy, {{H, list_to_integer(Port)}, []}}], ext_mod);
|
||||
[H] ->
|
||||
httpc:set_options([{proxy, {{H, 8080}, []}}], ext_mod);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
User = case getenv("PROXY_USER", "", [4]) of
|
||||
[U, Pass] -> [{proxy_user, U}, {proxy_password, Pass}];
|
||||
[U, Pass] -> [{proxy_auth, {U, Pass}}];
|
||||
_ -> []
|
||||
end,
|
||||
case p1_http:request(get, Url, Hdrs, [], Host++User++UsrOpts++[{version, "HTTP/1.0"}]) of
|
||||
{ok, 200, Headers, Response} ->
|
||||
case httpc:request(get, {Url, []}, User, [{body_format, binary}], ext_mod) of
|
||||
{ok, {{_, 200, _}, Headers, Response}} ->
|
||||
{ok, Headers, Response};
|
||||
{ok, Code, _Headers, Response} ->
|
||||
{ok, {{_, Code, _}, _Headers, Response}} ->
|
||||
{error, {Code, Response}};
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
|
||||
+35
-15
@@ -152,7 +152,7 @@ sort_modules(Host, ModOpts) ->
|
||||
[Mod, DepMod]),
|
||||
?ERROR_MSG(ErrTxt, []),
|
||||
digraph:del_vertex(G, Mod),
|
||||
maybe_halt_ejabberd(ErrTxt);
|
||||
maybe_halt_ejabberd();
|
||||
false when Type == soft ->
|
||||
?WARNING_MSG("Module '~s' is recommended for "
|
||||
"module '~s' but is not found in "
|
||||
@@ -240,11 +240,11 @@ start_module(Host, Module, Opts0, Order, NeedValidation) ->
|
||||
erlang:get_stacktrace()])
|
||||
end,
|
||||
?CRITICAL_MSG(ErrorText, []),
|
||||
maybe_halt_ejabberd(ErrorText),
|
||||
maybe_halt_ejabberd(),
|
||||
erlang:raise(Class, Reason, erlang:get_stacktrace())
|
||||
end;
|
||||
{error, ErrorText} ->
|
||||
maybe_halt_ejabberd(ErrorText)
|
||||
{error, _ErrorText} ->
|
||||
maybe_halt_ejabberd()
|
||||
end.
|
||||
|
||||
-spec reload_modules(binary()) -> ok.
|
||||
@@ -318,14 +318,13 @@ store_options(Host, Module, Opts, Order) ->
|
||||
#ejabberd_module{module_host = {Module, Host},
|
||||
opts = Opts, order = Order}).
|
||||
|
||||
maybe_halt_ejabberd(ErrorText) ->
|
||||
maybe_halt_ejabberd() ->
|
||||
case is_app_running(ejabberd) of
|
||||
false ->
|
||||
?CRITICAL_MSG("ejabberd initialization was aborted "
|
||||
"because a module start failed.",
|
||||
[]),
|
||||
timer:sleep(3000),
|
||||
erlang:halt(string:substr(lists:flatten(ErrorText), 1, 199));
|
||||
ejabberd:halt();
|
||||
true ->
|
||||
ok
|
||||
end.
|
||||
@@ -549,7 +548,7 @@ validate_opts(Host, Module, Opts0) ->
|
||||
end, [Module|SubMods]),
|
||||
Required = lists:filter(fun is_atom/1, DefaultOpts),
|
||||
try
|
||||
Opts = merge_opts(Opts0, DefaultOpts),
|
||||
Opts = merge_opts(Opts0, DefaultOpts, Module),
|
||||
{ok, case get_validators(Host, {Module, SubMods}) of
|
||||
undef ->
|
||||
Opts;
|
||||
@@ -646,8 +645,8 @@ list_known_opts(Host, Module) ->
|
||||
Module:mod_opt_type('')
|
||||
end.
|
||||
|
||||
-spec merge_opts(opts(), opts()) -> opts().
|
||||
merge_opts(Opts, DefaultOpts) ->
|
||||
-spec merge_opts(opts(), opts(), module()) -> opts().
|
||||
merge_opts(Opts, DefaultOpts, Module) ->
|
||||
Result =
|
||||
lists:foldr(
|
||||
fun({Opt, Default}, Acc) ->
|
||||
@@ -655,7 +654,16 @@ merge_opts(Opts, DefaultOpts) ->
|
||||
{_, Val} ->
|
||||
case Default of
|
||||
[{A, _}|_] when is_atom(A) andalso is_list(Val) ->
|
||||
[{Opt, merge_opts(Val, Default)}|Acc];
|
||||
case is_opt_list(Val) of
|
||||
true ->
|
||||
[{Opt, merge_opts(Val, Default, Module)}|Acc];
|
||||
false ->
|
||||
?ERROR_MSG(
|
||||
"Ignoring invalid value '~p' for "
|
||||
"option '~s' of module '~s'",
|
||||
[Val, Opt, Module]),
|
||||
[{Opt, Default}|Acc]
|
||||
end;
|
||||
Val ->
|
||||
[{Opt, Default}|Acc];
|
||||
_ ->
|
||||
@@ -834,9 +842,9 @@ get_hosts(Opts, Prefix) ->
|
||||
Hosts
|
||||
end.
|
||||
|
||||
-spec get_module_proc(binary(), {frontend, atom()} | atom()) -> atom().
|
||||
get_module_proc(Host, {frontend, Base}) ->
|
||||
get_module_proc(<<"frontend_", Host/binary>>, Base);
|
||||
-spec get_module_proc(binary() | global, atom()) -> atom().
|
||||
get_module_proc(global, Base) ->
|
||||
get_module_proc(<<"global">>, Base);
|
||||
get_module_proc(Host, Base) ->
|
||||
binary_to_atom(
|
||||
<<(erlang:atom_to_binary(Base, latin1))/binary, "_", Host/binary>>,
|
||||
@@ -874,12 +882,24 @@ is_equal_opt(Opt, NewOpts, OldOpts) ->
|
||||
true
|
||||
end.
|
||||
|
||||
-spec is_opt_list(term()) -> boolean().
|
||||
is_opt_list([]) ->
|
||||
true;
|
||||
is_opt_list(L) when is_list(L) ->
|
||||
lists:all(
|
||||
fun({Opt, _Val}) -> is_atom(Opt);
|
||||
(_) -> false
|
||||
end, L);
|
||||
is_opt_list(_) ->
|
||||
false.
|
||||
|
||||
-spec opt_type(modules) -> fun(([{atom(), list()}]) -> [{atom(), list()}]);
|
||||
(atom()) -> [atom()].
|
||||
opt_type(modules) ->
|
||||
fun(Mods) ->
|
||||
lists:map(
|
||||
fun({M, A}) when is_atom(M), is_list(A) ->
|
||||
fun({M, A}) when is_atom(M) ->
|
||||
true = is_opt_list(A),
|
||||
{M, A}
|
||||
end, Mods)
|
||||
end;
|
||||
|
||||
+25
-2
@@ -29,7 +29,7 @@
|
||||
|
||||
%% API
|
||||
-export([tolower/1, term_to_base64/1, base64_to_term/1, ip_to_list/1,
|
||||
hex_to_bin/1, hex_to_base64/1, expand_keyword/3,
|
||||
hex_to_bin/1, hex_to_base64/1, url_encode/1, expand_keyword/3,
|
||||
atom_to_binary/1, binary_to_atom/1, tuple_to_binary/1,
|
||||
l2i/1, i2l/1, i2l/2, expr_to_term/1, term_to_expr/1,
|
||||
now_to_usec/1, usec_to_now/1, encode_pid/1, decode_pid/2,
|
||||
@@ -65,7 +65,7 @@ term_to_base64(Term) ->
|
||||
base64_to_term(Base64) ->
|
||||
try binary_to_term(base64:decode(Base64), [safe]) of
|
||||
Term -> {term, Term}
|
||||
catch _:badarg ->
|
||||
catch _:_ ->
|
||||
error
|
||||
end.
|
||||
|
||||
@@ -105,6 +105,10 @@ hex_to_bin([H1, H2 | T], Acc) ->
|
||||
hex_to_base64(Hex) ->
|
||||
base64:encode(hex_to_bin(Hex)).
|
||||
|
||||
-spec url_encode(binary()) -> binary().
|
||||
url_encode(A) ->
|
||||
url_encode(A, <<>>).
|
||||
|
||||
-spec expand_keyword(binary(), binary(), binary()) -> binary().
|
||||
expand_keyword(Keyword, Input, Replacement) ->
|
||||
Parts = binary:split(Input, Keyword, [global]),
|
||||
@@ -262,6 +266,25 @@ read_js(File) ->
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
-spec url_encode(binary(), binary()) -> binary().
|
||||
url_encode(<<H:8, T/binary>>, Acc) when
|
||||
(H >= $a andalso H =< $z) orelse
|
||||
(H >= $A andalso H =< $Z) orelse
|
||||
(H >= $0 andalso H =< $9) orelse
|
||||
H == $_ orelse
|
||||
H == $. orelse
|
||||
H == $- orelse
|
||||
H == $/ orelse
|
||||
H == $: ->
|
||||
url_encode(T, <<Acc/binary, H>>);
|
||||
url_encode(<<H:8, T/binary>>, Acc) ->
|
||||
case integer_to_list(H, 16) of
|
||||
[X, Y] -> url_encode(T, <<Acc/binary, $%, X, Y>>);
|
||||
[X] -> url_encode(T, <<Acc/binary, $%, $0, X>>)
|
||||
end;
|
||||
url_encode(<<>>, Acc) ->
|
||||
Acc.
|
||||
|
||||
-spec set_node_id(string(), binary()) -> pid().
|
||||
set_node_id(PidStr, NodeBin) ->
|
||||
ExtPidStr = erlang:pid_to_list(
|
||||
|
||||
+11
-10
@@ -38,7 +38,8 @@
|
||||
-export([read_caps/1, list_features/1, caps_stream_features/2,
|
||||
disco_features/5, disco_identity/5, disco_info/5,
|
||||
get_features/2, export/1, import_info/0, import/5,
|
||||
get_user_caps/2, import_start/2, import_stop/2]).
|
||||
get_user_caps/2, import_start/2, import_stop/2,
|
||||
compute_disco_hash/2, is_valid_node/1]).
|
||||
|
||||
%% gen_mod callbacks
|
||||
-export([start/2, stop/1, reload/3, depends/2]).
|
||||
@@ -412,13 +413,13 @@ make_my_disco_hash(Host) ->
|
||||
DiscoInfo = #disco_info{identities = Identities,
|
||||
features = Feats,
|
||||
xdata = Info},
|
||||
make_disco_hash(DiscoInfo, sha);
|
||||
compute_disco_hash(DiscoInfo, sha);
|
||||
_Err -> <<"">>
|
||||
end.
|
||||
|
||||
-type digest_type() :: md5 | sha | sha224 | sha256 | sha384 | sha512.
|
||||
-spec make_disco_hash(disco_info(), digest_type()) -> binary().
|
||||
make_disco_hash(DiscoInfo, Algo) ->
|
||||
-spec compute_disco_hash(disco_info(), digest_type()) -> binary().
|
||||
compute_disco_hash(DiscoInfo, Algo) ->
|
||||
Concat = list_to_binary([concat_identities(DiscoInfo),
|
||||
concat_features(DiscoInfo), concat_info(DiscoInfo)]),
|
||||
base64:encode(case Algo of
|
||||
@@ -434,17 +435,17 @@ make_disco_hash(DiscoInfo, Algo) ->
|
||||
check_hash(Caps, DiscoInfo) ->
|
||||
case Caps#caps.hash of
|
||||
<<"md5">> ->
|
||||
Caps#caps.version == make_disco_hash(DiscoInfo, md5);
|
||||
Caps#caps.version == compute_disco_hash(DiscoInfo, md5);
|
||||
<<"sha-1">> ->
|
||||
Caps#caps.version == make_disco_hash(DiscoInfo, sha);
|
||||
Caps#caps.version == compute_disco_hash(DiscoInfo, sha);
|
||||
<<"sha-224">> ->
|
||||
Caps#caps.version == make_disco_hash(DiscoInfo, sha224);
|
||||
Caps#caps.version == compute_disco_hash(DiscoInfo, sha224);
|
||||
<<"sha-256">> ->
|
||||
Caps#caps.version == make_disco_hash(DiscoInfo, sha256);
|
||||
Caps#caps.version == compute_disco_hash(DiscoInfo, sha256);
|
||||
<<"sha-384">> ->
|
||||
Caps#caps.version == make_disco_hash(DiscoInfo, sha384);
|
||||
Caps#caps.version == compute_disco_hash(DiscoInfo, sha384);
|
||||
<<"sha-512">> ->
|
||||
Caps#caps.version == make_disco_hash(DiscoInfo, sha512);
|
||||
Caps#caps.version == compute_disco_hash(DiscoInfo, sha512);
|
||||
_ -> true
|
||||
end.
|
||||
|
||||
|
||||
@@ -37,8 +37,8 @@
|
||||
-export([filter_presence/1, filter_chat_states/1,
|
||||
filter_pep/1, filter_other/1,
|
||||
c2s_stream_started/2, add_stream_feature/2,
|
||||
c2s_copy_session/2, c2s_authenticated_packet/2,
|
||||
c2s_session_resumed/1]).
|
||||
c2s_authenticated_packet/2, csi_activity/2,
|
||||
c2s_copy_session/2, c2s_session_resumed/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -164,6 +164,8 @@ register_hooks(Host) ->
|
||||
add_stream_feature, 50),
|
||||
ejabberd_hooks:add(c2s_authenticated_packet, Host, ?MODULE,
|
||||
c2s_authenticated_packet, 50),
|
||||
ejabberd_hooks:add(csi_activity, Host, ?MODULE,
|
||||
csi_activity, 50),
|
||||
ejabberd_hooks:add(c2s_copy_session, Host, ?MODULE,
|
||||
c2s_copy_session, 50),
|
||||
ejabberd_hooks:add(c2s_session_resumed, Host, ?MODULE,
|
||||
@@ -179,6 +181,8 @@ unregister_hooks(Host) ->
|
||||
add_stream_feature, 50),
|
||||
ejabberd_hooks:delete(c2s_authenticated_packet, Host, ?MODULE,
|
||||
c2s_authenticated_packet, 50),
|
||||
ejabberd_hooks:delete(csi_activity, Host, ?MODULE,
|
||||
csi_activity, 50),
|
||||
ejabberd_hooks:delete(c2s_copy_session, Host, ?MODULE,
|
||||
c2s_copy_session, 50),
|
||||
ejabberd_hooks:delete(c2s_session_resumed, Host, ?MODULE,
|
||||
@@ -194,14 +198,20 @@ c2s_stream_started(State, _) ->
|
||||
init_csi_state(State).
|
||||
|
||||
-spec c2s_authenticated_packet(c2s_state(), xmpp_element()) -> c2s_state().
|
||||
c2s_authenticated_packet(C2SState, #csi{type = active}) ->
|
||||
C2SState1 = C2SState#{csi_state => active},
|
||||
flush_queue(C2SState1);
|
||||
c2s_authenticated_packet(C2SState, #csi{type = inactive}) ->
|
||||
C2SState#{csi_state => inactive};
|
||||
c2s_authenticated_packet(#{lserver := LServer} = C2SState, #csi{type = active}) ->
|
||||
ejabberd_hooks:run_fold(csi_activity, LServer, C2SState, [active]);
|
||||
c2s_authenticated_packet(#{lserver := LServer} = C2SState, #csi{type = inactive}) ->
|
||||
ejabberd_hooks:run_fold(csi_activity, LServer, C2SState, [inactive]);
|
||||
c2s_authenticated_packet(C2SState, _) ->
|
||||
C2SState.
|
||||
|
||||
-spec csi_activity(c2s_state(), active | inactive) -> c2s_state().
|
||||
csi_activity(C2SState, active) ->
|
||||
C2SState1 = C2SState#{csi_state => active},
|
||||
flush_queue(C2SState1);
|
||||
csi_activity(C2SState, inactive) ->
|
||||
C2SState#{csi_state => inactive}.
|
||||
|
||||
-spec c2s_copy_session(c2s_state(), c2s_state()) -> c2s_state().
|
||||
c2s_copy_session(C2SState, #{csi_queue := Q}) ->
|
||||
C2SState#{csi_queue => Q};
|
||||
|
||||
+16
-48
@@ -31,7 +31,7 @@
|
||||
-define(SERVICE_REQUEST_TIMEOUT, 5000). % 5 seconds.
|
||||
-define(SLOT_TIMEOUT, 18000000). % 5 hours.
|
||||
-define(FORMAT(Error), file:format_error(Error)).
|
||||
-define(URL_ENC(URL), binary_to_list(ejabberd_http:url_encode(URL))).
|
||||
-define(URL_ENC(URL), binary_to_list(misc:url_encode(URL))).
|
||||
-define(ADDR_TO_STR(IP), ejabberd_config:may_hide_data(misc:ip_to_list(IP))).
|
||||
-define(STR_TO_INT(Str, B), binary_to_integer(iolist_to_binary(Str), B)).
|
||||
-define(DEFAULT_CONTENT_TYPE, <<"application/octet-stream">>).
|
||||
@@ -43,6 +43,7 @@
|
||||
{<<".gz">>, <<"application/x-gzip">>},
|
||||
{<<".jpeg">>, <<"image/jpeg">>},
|
||||
{<<".jpg">>, <<"image/jpeg">>},
|
||||
{<<".m4a">>, <<"audio/mp4">>},
|
||||
{<<".mp3">>, <<"audio/mpeg">>},
|
||||
{<<".mp4">>, <<"video/mp4">>},
|
||||
{<<".mpeg">>, <<"video/mpeg">>},
|
||||
@@ -125,7 +126,6 @@
|
||||
%% gen_mod/supervisor callbacks.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec start(binary(), gen_mod:opts()) -> {ok, pid()}.
|
||||
|
||||
start(ServerHost, Opts) ->
|
||||
case gen_mod:get_opt(rm_on_unregister, Opts) of
|
||||
true ->
|
||||
@@ -138,7 +138,6 @@ start(ServerHost, Opts) ->
|
||||
gen_mod:start_child(?MODULE, ServerHost, Opts, Proc).
|
||||
|
||||
-spec stop(binary()) -> ok | {error, any()}.
|
||||
|
||||
stop(ServerHost) ->
|
||||
case gen_mod:get_module_opt(ServerHost, ?MODULE, rm_on_unregister) of
|
||||
true ->
|
||||
@@ -151,7 +150,6 @@ stop(ServerHost) ->
|
||||
gen_mod:stop_child(Proc).
|
||||
|
||||
-spec mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()].
|
||||
|
||||
mod_opt_type(host) ->
|
||||
fun iolist_to_binary/1;
|
||||
mod_opt_type(hosts) ->
|
||||
@@ -217,6 +215,7 @@ mod_opt_type(thumbnail) ->
|
||||
false
|
||||
end.
|
||||
|
||||
-spec mod_options(binary()) -> [{atom(), any()}].
|
||||
mod_options(_Host) ->
|
||||
[{host, <<"upload.@HOST@">>},
|
||||
{hosts, []},
|
||||
@@ -236,16 +235,13 @@ mod_options(_Host) ->
|
||||
{thumbnail, true}].
|
||||
|
||||
-spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}].
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% gen_server callbacks.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-spec init(list()) -> {ok, state()}.
|
||||
|
||||
init([ServerHost, Opts]) ->
|
||||
process_flag(trap_exit, true),
|
||||
Hosts = gen_mod:get_opt_hosts(ServerHost, Opts),
|
||||
@@ -293,7 +289,6 @@ init([ServerHost, Opts]) ->
|
||||
pos_integer() | undefined,
|
||||
pos_integer() | undefined}, state()} |
|
||||
{reply, {error, atom()}, state()} | {noreply, state()}.
|
||||
|
||||
handle_call({use_slot, Slot, Size}, _From,
|
||||
#state{file_mode = FileMode,
|
||||
dir_mode = DirMode,
|
||||
@@ -323,13 +318,11 @@ handle_call(Request, From, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
-spec handle_cast(_, state()) -> {noreply, state()}.
|
||||
|
||||
handle_cast(Request, State) ->
|
||||
?ERROR_MSG("Got unexpected request: ~p", [Request]),
|
||||
{noreply, State}.
|
||||
|
||||
-spec handle_info(timeout | _, state()) -> {noreply, state()}.
|
||||
|
||||
handle_info({route, #iq{lang = Lang} = Packet}, State) ->
|
||||
try xmpp:decode_els(Packet) of
|
||||
IQ ->
|
||||
@@ -361,13 +354,11 @@ handle_info(Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
-spec terminate(normal | shutdown | {shutdown, _} | _, state()) -> ok.
|
||||
|
||||
terminate(Reason, #state{server_host = ServerHost, hosts = Hosts}) ->
|
||||
?DEBUG("Stopping HTTP upload process for ~s: ~p", [ServerHost, Reason]),
|
||||
lists:foreach(fun ejabberd_router:unregister_route/1, Hosts).
|
||||
|
||||
-spec code_change({down, _} | _, state(), _) -> {ok, state()}.
|
||||
|
||||
code_change(_OldVsn, #state{server_host = ServerHost} = State, _Extra) ->
|
||||
?DEBUG("Updating HTTP upload process for ~s", [ServerHost]),
|
||||
{ok, State}.
|
||||
@@ -375,10 +366,8 @@ code_change(_OldVsn, #state{server_host = ServerHost} = State, _Extra) ->
|
||||
%%--------------------------------------------------------------------
|
||||
%% ejabberd_http callback.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-spec process([binary()], #request{})
|
||||
-> {pos_integer(), [{binary(), binary()}], binary()}.
|
||||
|
||||
process(LocalPath, #request{method = Method, host = Host, ip = IP})
|
||||
when length(LocalPath) < 3,
|
||||
Method == 'PUT' orelse
|
||||
@@ -483,9 +472,7 @@ process(_LocalPath, #request{method = Method, host = Host, ip = IP}) ->
|
||||
%%--------------------------------------------------------------------
|
||||
%% Exported utility functions.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-spec get_proc_name(binary(), atom()) -> atom().
|
||||
|
||||
get_proc_name(ServerHost, ModuleName) ->
|
||||
PutURL = gen_mod:get_module_opt(ServerHost, ?MODULE, put_url),
|
||||
{ok, {_Scheme, _UserInfo, Host, _Port, Path, _Query}} =
|
||||
@@ -494,13 +481,11 @@ get_proc_name(ServerHost, ModuleName) ->
|
||||
gen_mod:get_module_proc(ProcPrefix, ModuleName).
|
||||
|
||||
-spec expand_home(binary()) -> binary().
|
||||
|
||||
expand_home(Input) ->
|
||||
{ok, [[Home]]} = init:get_argument(home),
|
||||
misc:expand_keyword(<<"@HOME@">>, Input, Home).
|
||||
|
||||
-spec expand_host(binary(), binary()) -> binary().
|
||||
|
||||
expand_host(Input, Host) ->
|
||||
misc:expand_keyword(<<"@HOST@">>, Input, Host).
|
||||
|
||||
@@ -511,7 +496,6 @@ expand_host(Input, Host) ->
|
||||
%% XMPP request handling.
|
||||
|
||||
-spec process_iq(iq(), state()) -> {iq(), state()} | iq() | not_request.
|
||||
|
||||
process_iq(#iq{type = get, lang = Lang, sub_els = [#disco_info{}]} = IQ,
|
||||
#state{server_host = ServerHost, name = Name}) ->
|
||||
AddInfo = ejabberd_hooks:run_fold(disco_info, ServerHost, [],
|
||||
@@ -539,7 +523,6 @@ process_iq(#iq{}, _State) ->
|
||||
|
||||
-spec process_slot_request(iq(), binary(), pos_integer(), binary(), binary(),
|
||||
state()) -> {iq(), state()} | iq().
|
||||
|
||||
process_slot_request(#iq{lang = Lang, from = From} = IQ,
|
||||
File, Size, CType, XMLNS,
|
||||
#state{server_host = ServerHost,
|
||||
@@ -570,7 +553,6 @@ process_slot_request(#iq{lang = Lang, from = From} = IQ,
|
||||
|
||||
-spec create_slot(state(), jid(), binary(), pos_integer(), binary(), binary())
|
||||
-> {ok, slot()} | {ok, binary(), binary()} | {error, xmlel()}.
|
||||
|
||||
create_slot(#state{service_url = undefined, max_size = MaxSize},
|
||||
JID, File, Size, _ContentType, Lang) when MaxSize /= infinity,
|
||||
Size > MaxSize ->
|
||||
@@ -647,53 +629,54 @@ create_slot(#state{service_url = ServiceURL},
|
||||
end.
|
||||
|
||||
-spec add_slot(slot(), pos_integer(), timer:tref(), state()) -> state().
|
||||
|
||||
add_slot(Slot, Size, Timer, #state{slots = Slots} = State) ->
|
||||
NewSlots = maps:put(Slot, {Size, Timer}, Slots),
|
||||
State#state{slots = NewSlots}.
|
||||
|
||||
-spec get_slot(slot(), state()) -> {ok, {pos_integer(), timer:tref()}} | error.
|
||||
|
||||
get_slot(Slot, #state{slots = Slots}) ->
|
||||
maps:find(Slot, Slots).
|
||||
|
||||
-spec del_slot(slot(), state()) -> state().
|
||||
|
||||
del_slot(Slot, #state{slots = Slots} = State) ->
|
||||
NewSlots = maps:remove(Slot, Slots),
|
||||
State#state{slots = NewSlots}.
|
||||
|
||||
-spec mk_slot(slot(), state(), binary()) -> upload_slot();
|
||||
(binary(), binary(), binary()) -> upload_slot().
|
||||
|
||||
mk_slot(Slot, #state{put_url = PutPrefix, get_url = GetPrefix}, XMLNS) ->
|
||||
PutURL = str:join([PutPrefix | Slot], <<$/>>),
|
||||
GetURL = str:join([GetPrefix | Slot], <<$/>>),
|
||||
mk_slot(PutURL, GetURL, XMLNS);
|
||||
mk_slot(PutURL, GetURL, ?NS_HTTP_UPLOAD_0) ->
|
||||
#upload_slot_0{get = GetURL, put = PutURL, xmlns = ?NS_HTTP_UPLOAD_0};
|
||||
#upload_slot_0{get = misc:url_encode(GetURL),
|
||||
put = misc:url_encode(PutURL),
|
||||
xmlns = ?NS_HTTP_UPLOAD_0};
|
||||
mk_slot(PutURL, GetURL, XMLNS) ->
|
||||
#upload_slot{get = GetURL, put = PutURL, xmlns = XMLNS}.
|
||||
#upload_slot{get = misc:url_encode(GetURL),
|
||||
put = misc:url_encode(PutURL),
|
||||
xmlns = XMLNS}.
|
||||
|
||||
-spec make_user_string(jid(), sha1 | node) -> binary().
|
||||
|
||||
make_user_string(#jid{luser = U, lserver = S}, sha1) ->
|
||||
str:sha(<<U/binary, $@, S/binary>>);
|
||||
make_user_string(#jid{luser = U}, node) ->
|
||||
re:replace(U, <<"[^a-zA-Z0-9_.-]">>, <<$_>>, [global, {return, binary}]).
|
||||
replace_special_chars(U).
|
||||
|
||||
-spec make_file_string(binary()) -> binary().
|
||||
|
||||
make_file_string(File) ->
|
||||
re:replace(File, <<"[^a-zA-Z0-9_.-]">>, <<$_>>, [global, {return, binary}]).
|
||||
replace_special_chars(File).
|
||||
|
||||
-spec replace_special_chars(binary()) -> binary().
|
||||
replace_special_chars(S) ->
|
||||
re:replace(S, <<"[^\\p{Xan}_.-]">>, <<$_>>,
|
||||
[unicode, global, {return, binary}]).
|
||||
|
||||
-spec yield_content_type(binary()) -> binary().
|
||||
|
||||
yield_content_type(<<"">>) -> ?DEFAULT_CONTENT_TYPE;
|
||||
yield_content_type(Type) -> Type.
|
||||
|
||||
-spec iq_disco_info(binary(), binary(), binary(), [xdata()]) -> disco_info().
|
||||
|
||||
iq_disco_info(Host, Lang, Name, AddInfo) ->
|
||||
Form = case gen_mod:get_module_opt(Host, ?MODULE, max_size) of
|
||||
infinity ->
|
||||
@@ -726,7 +709,6 @@ iq_disco_info(Host, Lang, Name, AddInfo) ->
|
||||
%% HTTP request handling.
|
||||
|
||||
-spec parse_http_request(#request{}) -> {atom(), slot()}.
|
||||
|
||||
parse_http_request(#request{host = Host, path = Path}) ->
|
||||
PrefixLength = length(Path) - 3,
|
||||
{ProcURL, Slot} = if PrefixLength > 0 ->
|
||||
@@ -743,7 +725,6 @@ parse_http_request(#request{host = Host, path = Path}) ->
|
||||
integer() | undefined,
|
||||
binary(), slot(), boolean())
|
||||
-> ok | {ok, [{binary(), binary()}], binary()} | {error, term()}.
|
||||
|
||||
store_file(Path, Data, FileMode, DirMode, GetPrefix, Slot, Thumbnail) ->
|
||||
case do_store_file(Path, Data, FileMode, DirMode) of
|
||||
ok when Thumbnail ->
|
||||
@@ -776,7 +757,6 @@ store_file(Path, Data, FileMode, DirMode, GetPrefix, Slot, Thumbnail) ->
|
||||
integer() | undefined,
|
||||
integer() | undefined)
|
||||
-> ok | {error, term()}.
|
||||
|
||||
do_store_file(Path, Data, FileMode, DirMode) ->
|
||||
try
|
||||
ok = filelib:ensure_dir(Path),
|
||||
@@ -805,7 +785,6 @@ do_store_file(Path, Data, FileMode, DirMode) ->
|
||||
end.
|
||||
|
||||
-spec guess_content_type(binary()) -> binary().
|
||||
|
||||
guess_content_type(FileName) ->
|
||||
mod_http_fileserver:content_type(FileName,
|
||||
?DEFAULT_CONTENT_TYPE,
|
||||
@@ -813,20 +792,17 @@ guess_content_type(FileName) ->
|
||||
|
||||
-spec http_response(100..599)
|
||||
-> {pos_integer(), [{binary(), binary()}], binary()}.
|
||||
|
||||
http_response(Code) ->
|
||||
http_response(Code, []).
|
||||
|
||||
-spec http_response(100..599, [{binary(), binary()}])
|
||||
-> {pos_integer(), [{binary(), binary()}], binary()}.
|
||||
|
||||
http_response(Code, ExtraHeaders) ->
|
||||
Message = <<(code_to_message(Code))/binary, $\n>>,
|
||||
http_response(Code, ExtraHeaders, Message).
|
||||
|
||||
-spec http_response(100..599, [{binary(), binary()}], binary())
|
||||
-> {pos_integer(), [{binary(), binary()}], binary()}.
|
||||
|
||||
http_response(Code, ExtraHeaders, Body) ->
|
||||
Headers = case proplists:is_defined(<<"Content-Type">>, ExtraHeaders) of
|
||||
true ->
|
||||
@@ -837,7 +813,6 @@ http_response(Code, ExtraHeaders, Body) ->
|
||||
{Code, Headers, Body}.
|
||||
|
||||
-spec code_to_message(100..599) -> binary().
|
||||
|
||||
code_to_message(201) -> <<"Upload successful.">>;
|
||||
code_to_message(403) -> <<"Forbidden.">>;
|
||||
code_to_message(404) -> <<"Not found.">>;
|
||||
@@ -849,9 +824,7 @@ code_to_message(_Code) -> <<"">>.
|
||||
%%--------------------------------------------------------------------
|
||||
%% Image manipulation stuff.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-spec identify(binary(), binary()) -> {ok, media_info()} | pass.
|
||||
|
||||
identify(Path, Data) ->
|
||||
case eimp:identify(Data) of
|
||||
{ok, Info} ->
|
||||
@@ -866,7 +839,6 @@ identify(Path, Data) ->
|
||||
end.
|
||||
|
||||
-spec convert(binary(), binary(), media_info()) -> {ok, binary(), media_info()} | pass.
|
||||
|
||||
convert(Path, Data, #media_info{type = T, width = W, height = H} = Info) ->
|
||||
if W * H >= 25000000 ->
|
||||
?DEBUG("The image ~s is more than 25 Mpix", [Path]),
|
||||
@@ -901,7 +873,6 @@ convert(Path, Data, #media_info{type = T, width = W, height = H} = Info) ->
|
||||
end.
|
||||
|
||||
-spec thumb_el(media_info(), binary()) -> xmlel().
|
||||
|
||||
thumb_el(#media_info{type = T, height = H, width = W}, URI) ->
|
||||
MimeType = <<"image/", (atom_to_binary(T, latin1))/binary>>,
|
||||
Thumb = #thumbnail{'media-type' = MimeType, uri = URI,
|
||||
@@ -911,9 +882,7 @@ thumb_el(#media_info{type = T, height = H, width = W}, URI) ->
|
||||
%%--------------------------------------------------------------------
|
||||
%% Remove user.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-spec remove_user(binary(), binary()) -> ok.
|
||||
|
||||
remove_user(User, Server) ->
|
||||
ServerHost = jid:nameprep(Server),
|
||||
DocRoot = gen_mod:get_module_opt(ServerHost, ?MODULE, docroot),
|
||||
@@ -933,7 +902,6 @@ remove_user(User, Server) ->
|
||||
ok.
|
||||
|
||||
-spec del_tree(file:filename_all()) -> ok | {error, term()}.
|
||||
|
||||
del_tree(Dir) when is_binary(Dir) ->
|
||||
del_tree(binary_to_list(Dir));
|
||||
del_tree(Dir) ->
|
||||
|
||||
@@ -70,19 +70,16 @@
|
||||
%% gen_mod/supervisor callbacks.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec start(binary(), gen_mod:opts()) -> {ok, pid()}.
|
||||
|
||||
start(ServerHost, Opts) ->
|
||||
Proc = mod_http_upload:get_proc_name(ServerHost, ?MODULE),
|
||||
gen_mod:start_child(?MODULE, ServerHost, Opts, Proc).
|
||||
|
||||
-spec stop(binary()) -> ok | {error, any()}.
|
||||
|
||||
stop(ServerHost) ->
|
||||
Proc = mod_http_upload:get_proc_name(ServerHost, ?MODULE),
|
||||
gen_mod:stop_child(Proc).
|
||||
|
||||
-spec mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()].
|
||||
|
||||
mod_opt_type(access_soft_quota) ->
|
||||
fun acl:shaper_rules_validator/1;
|
||||
mod_opt_type(access_hard_quota) ->
|
||||
@@ -92,20 +89,20 @@ mod_opt_type(max_days) ->
|
||||
(infinity) -> infinity
|
||||
end.
|
||||
|
||||
-spec mod_options(binary()) -> [{atom(), any()}].
|
||||
mod_options(_) ->
|
||||
[{access_soft_quota, soft_upload_quota},
|
||||
{access_hard_quota, hard_upload_quota},
|
||||
{max_days, infinity}].
|
||||
|
||||
-spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}].
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[{mod_http_upload, hard}].
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% gen_server callbacks.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-spec init(list()) -> {ok, state()}.
|
||||
init([ServerHost, Opts]) ->
|
||||
process_flag(trap_exit, true),
|
||||
AccessSoftQuota = gen_mod:get_opt(access_soft_quota, Opts),
|
||||
@@ -130,13 +127,11 @@ init([ServerHost, Opts]) ->
|
||||
timers = Timers}}.
|
||||
|
||||
-spec handle_call(_, {pid(), _}, state()) -> {noreply, state()}.
|
||||
|
||||
handle_call(Request, From, State) ->
|
||||
?ERROR_MSG("Got unexpected request from ~p: ~p", [From, Request]),
|
||||
{noreply, State}.
|
||||
|
||||
-spec handle_cast(_, state()) -> {noreply, state()}.
|
||||
|
||||
handle_cast({handle_slot_request, #jid{user = U, server = S} = JID, Path, Size},
|
||||
#state{server_host = ServerHost,
|
||||
access_soft_quota = AccessSoftQuota,
|
||||
@@ -194,7 +189,6 @@ handle_cast(Request, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
-spec handle_info(_, state()) -> {noreply, state()}.
|
||||
|
||||
handle_info(sweep, #state{server_host = ServerHost,
|
||||
docroot = DocRoot,
|
||||
max_days = MaxDays} = State)
|
||||
@@ -221,7 +215,6 @@ handle_info(Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
-spec terminate(normal | shutdown | {shutdown, _} | _, state()) -> ok.
|
||||
|
||||
terminate(Reason, #state{server_host = ServerHost, timers = Timers}) ->
|
||||
?DEBUG("Stopping upload quota process for ~s: ~p", [ServerHost, Reason]),
|
||||
ejabberd_hooks:delete(http_upload_slot_request, ServerHost, ?MODULE,
|
||||
@@ -229,7 +222,6 @@ terminate(Reason, #state{server_host = ServerHost, timers = Timers}) ->
|
||||
lists:foreach(fun timer:cancel/1, Timers).
|
||||
|
||||
-spec code_change({down, _} | _, state(), _) -> {ok, state()}.
|
||||
|
||||
code_change(_OldVsn, #state{server_host = ServerHost} = State, _Extra) ->
|
||||
?DEBUG("Updating upload quota process for ~s", [ServerHost]),
|
||||
{ok, State}.
|
||||
@@ -237,10 +229,8 @@ code_change(_OldVsn, #state{server_host = ServerHost} = State, _Extra) ->
|
||||
%%--------------------------------------------------------------------
|
||||
%% ejabberd_hooks callback.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-spec handle_slot_request(allow | deny, jid(), binary(),
|
||||
non_neg_integer(), binary()) -> allow | deny.
|
||||
|
||||
handle_slot_request(allow, #jid{lserver = ServerHost} = JID, Path, Size,
|
||||
_Lang) ->
|
||||
Proc = mod_http_upload:get_proc_name(ServerHost, ?MODULE),
|
||||
@@ -251,12 +241,10 @@ handle_slot_request(Acc, _JID, _Path, _Size, _Lang) -> Acc.
|
||||
%%--------------------------------------------------------------------
|
||||
%% Internal functions.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-spec enforce_quota(file:filename_all(), non_neg_integer(),
|
||||
non_neg_integer() | undefined, non_neg_integer(),
|
||||
non_neg_integer())
|
||||
-> non_neg_integer().
|
||||
|
||||
enforce_quota(_UserDir, SlotSize, OldSize, _MinSize, MaxSize)
|
||||
when is_integer(OldSize), OldSize + SlotSize =< MaxSize ->
|
||||
OldSize + SlotSize;
|
||||
@@ -282,7 +270,6 @@ enforce_quota(UserDir, SlotSize, _OldSize, MinSize, MaxSize) ->
|
||||
end.
|
||||
|
||||
-spec delete_old_files(file:filename_all(), integer()) -> ok.
|
||||
|
||||
delete_old_files(UserDir, CutOff) ->
|
||||
FileInfo = gather_file_info(UserDir),
|
||||
case [Path || {Path, _Size, Time} <- FileInfo, Time < CutOff] of
|
||||
@@ -295,7 +282,6 @@ delete_old_files(UserDir, CutOff) ->
|
||||
|
||||
-spec gather_file_info(file:filename_all())
|
||||
-> [{binary(), non_neg_integer(), non_neg_integer()}].
|
||||
|
||||
gather_file_info(Dir) when is_binary(Dir) ->
|
||||
gather_file_info(binary_to_list(Dir));
|
||||
gather_file_info(Dir) ->
|
||||
@@ -330,7 +316,6 @@ gather_file_info(Dir) ->
|
||||
end.
|
||||
|
||||
-spec del_file_and_dir(file:name_all()) -> ok.
|
||||
|
||||
del_file_and_dir(File) ->
|
||||
case file:delete(File) of
|
||||
ok ->
|
||||
@@ -347,7 +332,6 @@ del_file_and_dir(File) ->
|
||||
end.
|
||||
|
||||
-spec secs_since_epoch() -> non_neg_integer().
|
||||
|
||||
secs_since_epoch() ->
|
||||
{MegaSecs, Secs, _MicroSecs} = os:timestamp(),
|
||||
MegaSecs * 1000000 + Secs.
|
||||
|
||||
+57
-2
@@ -40,7 +40,7 @@
|
||||
muc_process_iq/2, muc_filter_message/3, message_is_archived/3,
|
||||
delete_old_messages/2, get_commands_spec/0, msg_to_el/4,
|
||||
get_room_config/4, set_room_option/3, offline_message/1, export/1,
|
||||
mod_options/1]).
|
||||
mod_options/1, remove_mam_for_user_with_peer/3, remove_mam_for_user/2]).
|
||||
|
||||
-include("xmpp.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -69,6 +69,7 @@
|
||||
{[{binary(), non_neg_integer(), xmlel()}], boolean(), non_neg_integer()}.
|
||||
-callback use_cache(binary()) -> boolean().
|
||||
-callback cache_nodes(binary()) -> [node()].
|
||||
-callback remove_from_archive(binary(), binary(), jid() | none) -> ok | {error, any()}.
|
||||
|
||||
-optional_callbacks([use_cache/1, cache_nodes/1]).
|
||||
|
||||
@@ -258,6 +259,41 @@ remove_room(LServer, Name, Host) ->
|
||||
Mod:remove_room(LServer, LName, LHost),
|
||||
ok.
|
||||
|
||||
-spec remove_mam_for_user(binary(), binary()) ->
|
||||
{ok, binary()} | {error, binary()}.
|
||||
remove_mam_for_user(User, Server) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
case Mod:remove_from_archive(LUser, LServer, none) of
|
||||
ok ->
|
||||
{ok, <<"MAM archive removed">>};
|
||||
{error, Bin} when is_binary(Bin) ->
|
||||
{error, Bin};
|
||||
{error, _} ->
|
||||
{error, <<"Db returned error">>}
|
||||
end.
|
||||
|
||||
-spec remove_mam_for_user_with_peer(binary(), binary(), binary()) ->
|
||||
{ok, binary()} | {error, binary()}.
|
||||
remove_mam_for_user_with_peer(User, Server, Peer) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
try jid:decode(Peer) of
|
||||
Jid ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
case Mod:remove_from_archive(LUser, LServer, Jid) of
|
||||
ok ->
|
||||
{ok, <<"MAM archive removed">>};
|
||||
{error, Bin} when is_binary(Bin) ->
|
||||
{error, Bin};
|
||||
{error, _} ->
|
||||
{error, <<"Db returned error">>}
|
||||
end
|
||||
catch _:_ ->
|
||||
{error, <<"Invalid peer JID">>}
|
||||
end.
|
||||
|
||||
-spec get_room_config([muc_roomconfig:property()], mod_muc_room:state(),
|
||||
jid(), binary()) -> [muc_roomconfig:property()].
|
||||
get_room_config(Fields, RoomState, _From, _Lang) ->
|
||||
@@ -1080,7 +1116,26 @@ get_commands_spec() ->
|
||||
"Days to keep messages"],
|
||||
args_example = [<<"all">>, 31],
|
||||
args = [{type, binary}, {days, integer}],
|
||||
result = {res, rescode}}].
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = remove_mam_for_user, tags = [mam],
|
||||
desc = "Remove mam archive for user",
|
||||
module = ?MODULE, function = remove_mam_for_user,
|
||||
args = [{user, binary}, {server, binary}],
|
||||
args_desc = ["Username", "Server"],
|
||||
args_example = [<<"bob">>, <<"example.com">>],
|
||||
result = {res, restuple},
|
||||
result_desc = "Result tuple",
|
||||
result_example = {ok, <<"MAM archive removed">>}},
|
||||
#ejabberd_commands{name = remove_mam_for_user_with_peer, tags = [mam],
|
||||
desc = "Remove mam archive for user with peer",
|
||||
module = ?MODULE, function = remove_mam_for_user_with_peer,
|
||||
args = [{user, binary}, {server, binary}, {with, binary}],
|
||||
args_desc = ["Username", "Server", "Peer"],
|
||||
args_example = [<<"bob">>, <<"example.com">>, <<"anne@example.com">>],
|
||||
result = {res, restuple},
|
||||
result_desc = "Result tuple",
|
||||
result_example = {ok, <<"MAM archive removed">>}}
|
||||
].
|
||||
|
||||
mod_opt_type(assume_mam_usage) ->
|
||||
fun (B) when is_boolean(B) -> B end;
|
||||
|
||||
+19
-1
@@ -28,7 +28,7 @@
|
||||
|
||||
%% API
|
||||
-export([init/2, remove_user/2, remove_room/3, delete_old_messages/3,
|
||||
extended_fields/0, store/8, write_prefs/4, get_prefs/2, select/6]).
|
||||
extended_fields/0, store/8, write_prefs/4, get_prefs/2, select/6, remove_from_archive/3]).
|
||||
|
||||
-include_lib("stdlib/include/ms_transform.hrl").
|
||||
-include("xmpp.hrl").
|
||||
@@ -67,6 +67,24 @@ remove_user(LUser, LServer) ->
|
||||
remove_room(_LServer, LName, LHost) ->
|
||||
remove_user(LName, LHost).
|
||||
|
||||
remove_from_archive(LUser, LServer, none) ->
|
||||
US = {LUser, LServer},
|
||||
case mnesia:transaction(fun () -> mnesia:delete({archive_msg, US}) end) of
|
||||
{atomic, _} -> ok;
|
||||
{aborted, Reason} -> {error, Reason}
|
||||
end;
|
||||
remove_from_archive(LUser, LServer, WithJid) ->
|
||||
US = {LUser, LServer},
|
||||
Peer = jid:remove_resource(jid:split(WithJid)),
|
||||
F = fun () ->
|
||||
Msgs = mnesia:match_object(#archive_msg{us = US, bare_peer = Peer, _ = '_'}),
|
||||
lists:foreach(fun mnesia:delete_object/1, Msgs)
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, _} -> ok;
|
||||
{aborted, Reason} -> {error, Reason}
|
||||
end.
|
||||
|
||||
delete_old_messages(global, TimeStamp, Type) ->
|
||||
mnesia:change_table_copy_type(archive_msg, node(), disc_copies),
|
||||
Result = delete_old_user_messages(mnesia:dirty_first(archive_msg), TimeStamp, Type),
|
||||
|
||||
+15
-1
@@ -30,7 +30,7 @@
|
||||
|
||||
%% API
|
||||
-export([init/2, remove_user/2, remove_room/3, delete_old_messages/3,
|
||||
extended_fields/0, store/8, write_prefs/4, get_prefs/2, select/6, export/1]).
|
||||
extended_fields/0, store/8, write_prefs/4, get_prefs/2, select/6, export/1, remove_from_archive/3]).
|
||||
|
||||
-include_lib("stdlib/include/ms_transform.hrl").
|
||||
-include("xmpp.hrl").
|
||||
@@ -56,6 +56,20 @@ remove_room(LServer, LName, LHost) ->
|
||||
LUser = jid:encode({LName, LHost, <<>>}),
|
||||
remove_user(LUser, LServer).
|
||||
|
||||
remove_from_archive(LUser, LServer, none) ->
|
||||
case ejabberd_sql:sql_query(LServer,
|
||||
?SQL("delete from archive where username=%(LUser)s and %(LServer)H")) of
|
||||
{error, Reason} -> {error, Reason};
|
||||
_ -> ok
|
||||
end;
|
||||
remove_from_archive(LUser, LServer, WithJid) ->
|
||||
Peer = jid:encode(jid:remove_resource(WithJid)),
|
||||
case ejabberd_sql:sql_query(LServer,
|
||||
?SQL("delete from archive where username=%(LUser)s and %(LServer)H and bare_peer=%(Peer)s")) of
|
||||
{error, Reason} -> {error, Reason};
|
||||
_ -> ok
|
||||
end.
|
||||
|
||||
delete_old_messages(ServerHost, TimeStamp, Type) ->
|
||||
TS = now_to_usec(TimeStamp),
|
||||
case Type of
|
||||
|
||||
+31
-14
@@ -476,19 +476,28 @@ process_vcard(#iq{lang = Lang} = IQ) ->
|
||||
xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).
|
||||
|
||||
-spec process_register(iq()) -> iq().
|
||||
process_register(#iq{type = get, from = From, to = To, lang = Lang,
|
||||
sub_els = [#register{}]} = IQ) ->
|
||||
process_register(#iq{type = Type, from = From, to = To, lang = Lang,
|
||||
sub_els = [El = #register{}]} = IQ) ->
|
||||
Host = To#jid.lserver,
|
||||
ServerHost = ejabberd_router:host_of_route(Host),
|
||||
xmpp:make_iq_result(IQ, iq_get_register_info(ServerHost, Host, From, Lang));
|
||||
process_register(#iq{type = set, from = From, to = To,
|
||||
lang = Lang, sub_els = [El = #register{}]} = IQ) ->
|
||||
Host = To#jid.lserver,
|
||||
ServerHost = ejabberd_router:host_of_route(Host),
|
||||
case process_iq_register_set(ServerHost, Host, From, El, Lang) of
|
||||
{result, Result} ->
|
||||
xmpp:make_iq_result(IQ, Result);
|
||||
{error, Err} ->
|
||||
AccessRegister = gen_mod:get_module_opt(ServerHost, ?MODULE, access_register),
|
||||
case acl:match_rule(ServerHost, AccessRegister, From) of
|
||||
allow ->
|
||||
case Type of
|
||||
get ->
|
||||
xmpp:make_iq_result(
|
||||
IQ, iq_get_register_info(ServerHost, Host, From, Lang));
|
||||
set ->
|
||||
case process_iq_register_set(ServerHost, Host, From, El, Lang) of
|
||||
{result, Result} ->
|
||||
xmpp:make_iq_result(IQ, Result);
|
||||
{error, Err} ->
|
||||
xmpp:make_error(IQ, Err)
|
||||
end
|
||||
end;
|
||||
deny ->
|
||||
ErrText = <<"Access denied by service policy">>,
|
||||
Err = xmpp:err_forbidden(ErrText, Lang),
|
||||
xmpp:make_error(IQ, Err)
|
||||
end.
|
||||
|
||||
@@ -496,10 +505,11 @@ process_register(#iq{type = set, from = From, to = To,
|
||||
process_disco_info(#iq{type = set, lang = Lang} = IQ) ->
|
||||
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
|
||||
process_disco_info(#iq{type = get, to = To, lang = Lang,
|
||||
process_disco_info(#iq{type = get, from = From, to = To, lang = Lang,
|
||||
sub_els = [#disco_info{node = <<"">>}]} = IQ) ->
|
||||
ServerHost = ejabberd_router:host_of_route(To#jid.lserver),
|
||||
RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
|
||||
AccessRegister = gen_mod:get_module_opt(ServerHost, ?MODULE, access_register),
|
||||
X = ejabberd_hooks:run_fold(disco_info, ServerHost, [],
|
||||
[ServerHost, ?MODULE, <<"">>, Lang]),
|
||||
MAMFeatures = case gen_mod:is_loaded(ServerHost, mod_mam) of
|
||||
@@ -510,9 +520,13 @@ process_disco_info(#iq{type = get, to = To, lang = Lang,
|
||||
true -> [?NS_RSM];
|
||||
false -> []
|
||||
end,
|
||||
RegisterFeatures = case acl:match_rule(ServerHost, AccessRegister, From) of
|
||||
allow -> [?NS_REGISTER];
|
||||
deny -> []
|
||||
end,
|
||||
Features = [?NS_DISCO_INFO, ?NS_DISCO_ITEMS,
|
||||
?NS_REGISTER, ?NS_MUC, ?NS_VCARD, ?NS_MUCSUB, ?NS_MUC_UNIQUE
|
||||
| RSMFeatures ++ MAMFeatures],
|
||||
?NS_MUC, ?NS_VCARD, ?NS_MUCSUB, ?NS_MUC_UNIQUE
|
||||
| RegisterFeatures ++ RSMFeatures ++ MAMFeatures],
|
||||
Name = gen_mod:get_module_opt(ServerHost, ?MODULE, name),
|
||||
Identity = #identity{category = <<"conference">>,
|
||||
type = <<"text">>,
|
||||
@@ -862,6 +876,8 @@ mod_opt_type(access_create) ->
|
||||
fun acl:access_rules_validator/1;
|
||||
mod_opt_type(access_persistent) ->
|
||||
fun acl:access_rules_validator/1;
|
||||
mod_opt_type(access_register) ->
|
||||
fun acl:access_rules_validator/1;
|
||||
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
mod_opt_type(ram_db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
mod_opt_type(history_size) ->
|
||||
@@ -967,6 +983,7 @@ mod_options(Host) ->
|
||||
{access_admin, none},
|
||||
{access_create, all},
|
||||
{access_persistent, all},
|
||||
{access_register, all},
|
||||
{db_type, ejabberd_config:default_db(Host, ?MODULE)},
|
||||
{ram_db_type, ejabberd_config:default_ram_db(Host, ?MODULE)},
|
||||
{history_size, 20},
|
||||
|
||||
+7
-7
@@ -375,31 +375,31 @@ add_message_to_log(Nick1, Message, RoomJID, Opts,
|
||||
io_lib:format("<font class=\"ml\">~s ~s: ~s</font><br/>",
|
||||
[Nick, ?T(<<"leaves the room">>),
|
||||
htmlize(Reason, NoFollow, FileFormat)]);
|
||||
{kickban, <<"301">>, <<"">>} ->
|
||||
{kickban, 301, <<"">>} ->
|
||||
io_lib:format("<font class=\"mb\">~s ~s</font><br/>",
|
||||
[Nick, ?T(<<"has been banned">>)]);
|
||||
{kickban, <<"301">>, Reason} ->
|
||||
{kickban, 301, Reason} ->
|
||||
io_lib:format("<font class=\"mb\">~s ~s: ~s</font><br/>",
|
||||
[Nick, ?T(<<"has been banned">>),
|
||||
htmlize(Reason, FileFormat)]);
|
||||
{kickban, <<"307">>, <<"">>} ->
|
||||
{kickban, 307, <<"">>} ->
|
||||
io_lib:format("<font class=\"mk\">~s ~s</font><br/>",
|
||||
[Nick, ?T(<<"has been kicked">>)]);
|
||||
{kickban, <<"307">>, Reason} ->
|
||||
{kickban, 307, Reason} ->
|
||||
io_lib:format("<font class=\"mk\">~s ~s: ~s</font><br/>",
|
||||
[Nick, ?T(<<"has been kicked">>),
|
||||
htmlize(Reason, FileFormat)]);
|
||||
{kickban, <<"321">>, <<"">>} ->
|
||||
{kickban, 321, <<"">>} ->
|
||||
io_lib:format("<font class=\"mk\">~s ~s</font><br/>",
|
||||
[Nick,
|
||||
?T(<<"has been kicked because of an affiliation "
|
||||
"change">>)]);
|
||||
{kickban, <<"322">>, <<"">>} ->
|
||||
{kickban, 322, <<"">>} ->
|
||||
io_lib:format("<font class=\"mk\">~s ~s</font><br/>",
|
||||
[Nick,
|
||||
?T(<<"has been kicked because the room has "
|
||||
"been changed to members-only">>)]);
|
||||
{kickban, <<"332">>, <<"">>} ->
|
||||
{kickban, 332, <<"">>} ->
|
||||
io_lib:format("<font class=\"mk\">~s ~s</font><br/>",
|
||||
[Nick,
|
||||
?T(<<"has been kicked because of a system "
|
||||
|
||||
+279
-88
@@ -290,7 +290,7 @@ normal_state({route, <<"">>,
|
||||
process_iq_admin(From, IQ, StateData);
|
||||
?NS_MUC_OWNER ->
|
||||
process_iq_owner(From, IQ, StateData);
|
||||
?NS_DISCO_INFO when SubEl#disco_info.node == <<>> ->
|
||||
?NS_DISCO_INFO ->
|
||||
process_iq_disco_info(From, IQ, StateData);
|
||||
?NS_DISCO_ITEMS ->
|
||||
process_iq_disco_items(From, IQ, StateData);
|
||||
@@ -502,8 +502,8 @@ handle_event(destroy, StateName, StateData) ->
|
||||
handle_event({destroy, <<"">>}, StateName, StateData);
|
||||
handle_event({set_affiliations, Affiliations},
|
||||
StateName, StateData) ->
|
||||
{next_state, StateName,
|
||||
StateData#state{affiliations = Affiliations}};
|
||||
NewStateData = set_affiliations(Affiliations, StateData),
|
||||
{next_state, StateName, NewStateData};
|
||||
handle_event(_Event, StateName, StateData) ->
|
||||
{next_state, StateName, StateData}.
|
||||
|
||||
@@ -1264,58 +1264,132 @@ set_affiliation(JID, Affiliation, StateData) ->
|
||||
set_affiliation(JID, Affiliation, StateData, <<"">>).
|
||||
|
||||
-spec set_affiliation(jid(), affiliation(), state(), binary()) -> state().
|
||||
set_affiliation(JID, Affiliation,
|
||||
#state{config = #config{persistent = false}} = StateData,
|
||||
Reason) ->
|
||||
set_affiliation_fallback(JID, Affiliation, StateData, Reason);
|
||||
set_affiliation(JID, Affiliation, StateData, Reason) ->
|
||||
ServerHost = StateData#state.server_host,
|
||||
Room = StateData#state.room,
|
||||
Host = StateData#state.host,
|
||||
Mod = gen_mod:db_mod(ServerHost, mod_muc),
|
||||
case Mod:set_affiliation(ServerHost, Room, Host, JID, Affiliation, Reason) of
|
||||
ok ->
|
||||
StateData;
|
||||
{error, _} ->
|
||||
set_affiliation_fallback(JID, Affiliation, StateData, Reason)
|
||||
end.
|
||||
|
||||
-spec set_affiliation_fallback(jid(), affiliation(), state(), binary()) -> state().
|
||||
set_affiliation_fallback(JID, Affiliation, StateData, Reason) ->
|
||||
LJID = jid:remove_resource(jid:tolower(JID)),
|
||||
Affiliations = case Affiliation of
|
||||
none ->
|
||||
(?DICT):erase(LJID, StateData#state.affiliations);
|
||||
_ ->
|
||||
(?DICT):store(LJID, {Affiliation, Reason},
|
||||
StateData#state.affiliations)
|
||||
none ->
|
||||
(?DICT):erase(LJID, StateData#state.affiliations);
|
||||
_ ->
|
||||
(?DICT):store(LJID, {Affiliation, Reason},
|
||||
StateData#state.affiliations)
|
||||
end,
|
||||
StateData#state{affiliations = Affiliations}.
|
||||
|
||||
-spec get_affiliation(jid(), state()) -> affiliation().
|
||||
get_affiliation(JID, StateData) ->
|
||||
{_AccessRoute, _AccessCreate, AccessAdmin,
|
||||
_AccessPersistent} =
|
||||
StateData#state.access,
|
||||
Res = case acl:match_rule(StateData#state.server_host,
|
||||
AccessAdmin, JID)
|
||||
of
|
||||
allow -> owner;
|
||||
_ ->
|
||||
LJID = jid:tolower(JID),
|
||||
case (?DICT):find(LJID, StateData#state.affiliations) of
|
||||
{ok, Affiliation} -> Affiliation;
|
||||
_ ->
|
||||
LJID1 = jid:remove_resource(LJID),
|
||||
case (?DICT):find(LJID1, StateData#state.affiliations)
|
||||
of
|
||||
{ok, Affiliation} -> Affiliation;
|
||||
_ ->
|
||||
LJID2 = setelement(1, LJID, <<"">>),
|
||||
case (?DICT):find(LJID2,
|
||||
StateData#state.affiliations)
|
||||
of
|
||||
{ok, Affiliation} -> Affiliation;
|
||||
_ ->
|
||||
LJID3 = jid:remove_resource(LJID2),
|
||||
case (?DICT):find(LJID3,
|
||||
StateData#state.affiliations)
|
||||
of
|
||||
{ok, Affiliation} -> Affiliation;
|
||||
_ -> none
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
case Res of
|
||||
{A, _Reason} -> A;
|
||||
_ -> Res
|
||||
-spec set_affiliations(?TDICT, state()) -> state().
|
||||
set_affiliations(Affiliations,
|
||||
#state{config = #config{persistent = false}} = StateData) ->
|
||||
set_affiliations_fallback(Affiliations, StateData);
|
||||
set_affiliations(Affiliations, StateData) ->
|
||||
Room = StateData#state.room,
|
||||
Host = StateData#state.host,
|
||||
ServerHost = StateData#state.server_host,
|
||||
Mod = gen_mod:db_mod(ServerHost, mod_muc),
|
||||
case Mod:set_affiliations(ServerHost, Room, Host, Affiliations) of
|
||||
ok ->
|
||||
StateData;
|
||||
{error, _} ->
|
||||
set_affiliations_fallback(Affiliations, StateData)
|
||||
end.
|
||||
|
||||
-spec set_affiliations_fallback(?TDICT, state()) -> state().
|
||||
set_affiliations_fallback(Affiliations, StateData) ->
|
||||
StateData#state{affiliations = Affiliations}.
|
||||
|
||||
-spec get_affiliation(ljid() | jid(), state()) -> affiliation().
|
||||
get_affiliation(#jid{} = JID, StateData) ->
|
||||
case get_service_affiliation(JID, StateData) of
|
||||
owner ->
|
||||
owner;
|
||||
none ->
|
||||
case do_get_affiliation(JID, StateData) of
|
||||
{Affiliation, _Reason} -> Affiliation;
|
||||
Affiliation -> Affiliation
|
||||
end
|
||||
end;
|
||||
get_affiliation(LJID, StateData) ->
|
||||
get_affiliation(jid:make(LJID), StateData).
|
||||
|
||||
-spec do_get_affiliation(jid(), state()) -> affiliation().
|
||||
do_get_affiliation(JID, #state{config = #config{persistent = false}} = StateData) ->
|
||||
do_get_affiliation_fallback(JID, StateData);
|
||||
do_get_affiliation(JID, StateData) ->
|
||||
Room = StateData#state.room,
|
||||
Host = StateData#state.host,
|
||||
LServer = JID#jid.lserver,
|
||||
LUser = JID#jid.luser,
|
||||
ServerHost = StateData#state.server_host,
|
||||
Mod = gen_mod:db_mod(ServerHost, mod_muc),
|
||||
case Mod:get_affiliation(ServerHost, Room, Host, LUser, LServer) of
|
||||
{error, _} ->
|
||||
do_get_affiliation_fallback(JID, StateData);
|
||||
{ok, Affiliation} ->
|
||||
Affiliation
|
||||
end.
|
||||
|
||||
-spec do_get_affiliation_fallback(jid(), state()) -> affiliation().
|
||||
do_get_affiliation_fallback(JID, StateData) ->
|
||||
LJID = jid:tolower(JID),
|
||||
case (?DICT):find(LJID, StateData#state.affiliations) of
|
||||
{ok, Affiliation} -> Affiliation;
|
||||
_ ->
|
||||
LJID1 = jid:remove_resource(LJID),
|
||||
case (?DICT):find(LJID1, StateData#state.affiliations)
|
||||
of
|
||||
{ok, Affiliation} -> Affiliation;
|
||||
_ ->
|
||||
LJID2 = setelement(1, LJID, <<"">>),
|
||||
case (?DICT):find(LJID2,
|
||||
StateData#state.affiliations)
|
||||
of
|
||||
{ok, Affiliation} -> Affiliation;
|
||||
_ ->
|
||||
LJID3 = jid:remove_resource(LJID2),
|
||||
case (?DICT):find(LJID3,
|
||||
StateData#state.affiliations)
|
||||
of
|
||||
{ok, Affiliation} -> Affiliation;
|
||||
_ -> none
|
||||
end
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
-spec get_affiliations(state()) -> ?TDICT.
|
||||
get_affiliations(#state{config = #config{persistent = false}} = StateData) ->
|
||||
get_affiliations_callback(StateData);
|
||||
get_affiliations(StateData) ->
|
||||
Room = StateData#state.room,
|
||||
Host = StateData#state.host,
|
||||
ServerHost = StateData#state.server_host,
|
||||
Mod = gen_mod:db_mod(ServerHost, mod_muc),
|
||||
case Mod:get_affiliations(ServerHost, Room, Host) of
|
||||
{error, _} ->
|
||||
get_affiliations_callback(StateData);
|
||||
{ok, Affiliations} ->
|
||||
Affiliations
|
||||
end.
|
||||
|
||||
-spec get_affiliations_callback(state()) -> ?TDICT.
|
||||
get_affiliations_callback(StateData) ->
|
||||
StateData#state.affiliations.
|
||||
|
||||
-spec get_service_affiliation(jid(), state()) -> owner | none.
|
||||
get_service_affiliation(JID, StateData) ->
|
||||
{_AccessRoute, _AccessCreate, AccessAdmin,
|
||||
@@ -2068,12 +2142,30 @@ presence_broadcast_allowed(JID, StateData) ->
|
||||
-spec send_initial_presences_and_messages(
|
||||
jid(), binary(), presence(), state(), state()) -> ok.
|
||||
send_initial_presences_and_messages(From, Nick, Presence, NewState, OldState) ->
|
||||
send_self_presence(From, NewState),
|
||||
send_existing_presences(From, NewState),
|
||||
send_initial_presence(From, NewState, OldState),
|
||||
History = get_history(Nick, Presence, NewState),
|
||||
send_history(From, History, NewState),
|
||||
send_subject(From, OldState).
|
||||
|
||||
-spec send_self_presence(jid(), state()) -> ok.
|
||||
send_self_presence(JID, State) ->
|
||||
AvatarHash = (State#state.config)#config.vcard_xupdate,
|
||||
DiscoInfo = make_disco_info(JID, State),
|
||||
DiscoHash = mod_caps:compute_disco_hash(DiscoInfo, sha),
|
||||
Els1 = [#caps{hash = <<"sha-1">>,
|
||||
node = ?EJABBERD_URI,
|
||||
version = DiscoHash}],
|
||||
Els2 = if is_binary(AvatarHash) ->
|
||||
[#vcard_xupdate{hash = AvatarHash}|Els1];
|
||||
true ->
|
||||
Els1
|
||||
end,
|
||||
ejabberd_router:route(#presence{from = State#state.jid, to = JID,
|
||||
id = randoms:get_string(),
|
||||
sub_els = Els2}).
|
||||
|
||||
-spec send_initial_presence(jid(), state(), state()) -> ok.
|
||||
send_initial_presence(NJID, StateData, OldStateData) ->
|
||||
send_new_presence1(NJID, <<"">>, true, StateData, OldStateData).
|
||||
@@ -2580,16 +2672,35 @@ search_role(Role, StateData) ->
|
||||
(?DICT):to_list(StateData#state.users)).
|
||||
|
||||
-spec search_affiliation(affiliation(), state()) ->
|
||||
[{ljid(),
|
||||
affiliation() | {affiliation(), binary()}}].
|
||||
[{ljid(),
|
||||
affiliation() | {affiliation(), binary()}}].
|
||||
search_affiliation(Affiliation,
|
||||
#state{config = #config{persistent = false}} = StateData) ->
|
||||
search_affiliation_fallback(Affiliation, StateData);
|
||||
search_affiliation(Affiliation, StateData) ->
|
||||
lists:filter(fun ({_, A}) ->
|
||||
case A of
|
||||
{A1, _Reason} -> Affiliation == A1;
|
||||
_ -> Affiliation == A
|
||||
end
|
||||
end,
|
||||
(?DICT):to_list(StateData#state.affiliations)).
|
||||
Room = StateData#state.room,
|
||||
Host = StateData#state.host,
|
||||
ServerHost = StateData#state.server_host,
|
||||
Mod = gen_mod:db_mod(ServerHost, mod_muc),
|
||||
case Mod:search_affiliation(ServerHost, Room, Host, Affiliation) of
|
||||
{ok, AffiliationList} ->
|
||||
AffiliationList;
|
||||
{error, _} ->
|
||||
search_affiliation_fallback(Affiliation, StateData)
|
||||
end.
|
||||
|
||||
-spec search_affiliation_fallback(affiliation(), state()) ->
|
||||
[{ljid(),
|
||||
affiliation() | {affiliation(), binary()}}].
|
||||
search_affiliation_fallback(Affiliation, StateData) ->
|
||||
lists:filter(
|
||||
fun({_, A}) ->
|
||||
case A of
|
||||
{A1, _Reason} -> Affiliation == A1;
|
||||
_ -> Affiliation == A
|
||||
end
|
||||
end,
|
||||
(?DICT):to_list(StateData#state.affiliations)).
|
||||
|
||||
-spec process_admin_items_set(jid(), [muc_item()], binary(),
|
||||
#state{}) -> {result, undefined, #state{}} |
|
||||
@@ -3309,23 +3420,36 @@ set_config(Opts, Config, ServerHost, Lang) ->
|
||||
-spec change_config(#config{}, state()) -> {result, undefined, state()}.
|
||||
change_config(Config, StateData) ->
|
||||
send_config_change_info(Config, StateData),
|
||||
NSD = remove_subscriptions(StateData#state{config = Config}),
|
||||
case {(StateData#state.config)#config.persistent,
|
||||
Config#config.persistent}
|
||||
of
|
||||
{_, true} ->
|
||||
store_room(NSD);
|
||||
{true, false} ->
|
||||
mod_muc:forget_room(NSD#state.server_host,
|
||||
NSD#state.host, NSD#state.room);
|
||||
{false, false} -> ok
|
||||
end,
|
||||
StateData0 = StateData#state{config = Config},
|
||||
StateData1 = remove_subscriptions(StateData0),
|
||||
StateData2 =
|
||||
case {(StateData#state.config)#config.persistent,
|
||||
Config#config.persistent} of
|
||||
{WasPersistent, true} ->
|
||||
if not WasPersistent ->
|
||||
set_affiliations(StateData1#state.affiliations,
|
||||
StateData1);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
store_room(StateData1),
|
||||
StateData1;
|
||||
{true, false} ->
|
||||
Affiliations = get_affiliations(StateData),
|
||||
mod_muc:forget_room(StateData1#state.server_host,
|
||||
StateData1#state.host,
|
||||
StateData1#state.room),
|
||||
StateData1#state{affiliations = Affiliations};
|
||||
{false, false} ->
|
||||
StateData1
|
||||
end,
|
||||
case {(StateData#state.config)#config.members_only,
|
||||
Config#config.members_only}
|
||||
of
|
||||
{false, true} ->
|
||||
NSD1 = remove_nonmembers(NSD), {result, undefined, NSD1};
|
||||
_ -> {result, undefined, NSD}
|
||||
Config#config.members_only} of
|
||||
{false, true} ->
|
||||
StateData3 = remove_nonmembers(StateData2),
|
||||
{result, undefined, StateData3};
|
||||
_ ->
|
||||
{result, undefined, StateData2}
|
||||
end.
|
||||
|
||||
-spec send_config_change_info(#config{}, state()) -> ok.
|
||||
@@ -3344,12 +3468,15 @@ send_config_change_info(New, #state{config = Old} = StateData) ->
|
||||
end
|
||||
++
|
||||
case Old#config{anonymous = New#config.anonymous,
|
||||
vcard = New#config.vcard,
|
||||
logging = New#config.logging} of
|
||||
New -> [];
|
||||
_ -> [104]
|
||||
end,
|
||||
if Codes /= [] ->
|
||||
lists:foreach(
|
||||
fun({_LJID, #user{jid = JID}}) ->
|
||||
send_self_presence(JID, StateData#state{config = New})
|
||||
end, ?DICT:to_list(StateData#state.users)),
|
||||
Message = #message{type = groupchat,
|
||||
id = randoms:get_string(),
|
||||
sub_els = [#muc_user{status_codes = Codes}]},
|
||||
@@ -3377,7 +3504,8 @@ remove_nonmembers(StateData) ->
|
||||
StateData, (?DICT):to_list(get_users_and_subscribers(StateData))).
|
||||
|
||||
-spec set_opts([{atom(), any()}], state()) -> state().
|
||||
set_opts([], StateData) -> StateData;
|
||||
set_opts([], StateData) ->
|
||||
set_vcard_xupdate(StateData);
|
||||
set_opts([{Opt, Val} | Opts], StateData) ->
|
||||
NSD = case Opt of
|
||||
title ->
|
||||
@@ -3492,6 +3620,10 @@ set_opts([{Opt, Val} | Opts], StateData) ->
|
||||
StateData#state{config =
|
||||
(StateData#state.config)#config{vcard =
|
||||
Val}};
|
||||
vcard_xupdate ->
|
||||
StateData#state{config =
|
||||
(StateData#state.config)#config{vcard_xupdate =
|
||||
Val}};
|
||||
pubsub ->
|
||||
StateData#state{config =
|
||||
(StateData#state.config)#config{pubsub = Val}};
|
||||
@@ -3526,6 +3658,20 @@ set_opts([{Opt, Val} | Opts], StateData) ->
|
||||
end,
|
||||
set_opts(Opts, NSD).
|
||||
|
||||
set_vcard_xupdate(#state{config =
|
||||
#config{vcard = VCardRaw,
|
||||
vcard_xupdate = undefined} = Config} = State)
|
||||
when VCardRaw /= <<"">> ->
|
||||
case fxml_stream:parse_element(VCardRaw) of
|
||||
{error, _} ->
|
||||
State;
|
||||
El ->
|
||||
Hash = mod_vcard_xupdate:compute_hash(El),
|
||||
State#state{config = Config#config{vcard_xupdate = Hash}}
|
||||
end;
|
||||
set_vcard_xupdate(State) ->
|
||||
State.
|
||||
|
||||
-define(MAKE_CONFIG_OPT(Opt),
|
||||
{get_config_opt_name(Opt), element(Opt, Config)}).
|
||||
|
||||
@@ -3561,6 +3707,7 @@ make_opts(StateData) ->
|
||||
?MAKE_CONFIG_OPT(#config.presence_broadcast),
|
||||
?MAKE_CONFIG_OPT(#config.voice_request_min_interval),
|
||||
?MAKE_CONFIG_OPT(#config.vcard),
|
||||
?MAKE_CONFIG_OPT(#config.vcard_xupdate),
|
||||
?MAKE_CONFIG_OPT(#config.pubsub),
|
||||
{captcha_whitelist,
|
||||
(?SETS):to_list((StateData#state.config)#config.captcha_whitelist)},
|
||||
@@ -3633,12 +3780,8 @@ destroy_room(DEl, StateData) ->
|
||||
false -> Fiffalse
|
||||
end).
|
||||
|
||||
-spec process_iq_disco_info(jid(), iq(), state()) ->
|
||||
{result, disco_info()} | {error, stanza_error()}.
|
||||
process_iq_disco_info(_From, #iq{type = set, lang = Lang}, _StateData) ->
|
||||
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||
{error, xmpp:err_not_allowed(Txt, Lang)};
|
||||
process_iq_disco_info(_From, #iq{type = get, lang = Lang}, StateData) ->
|
||||
-spec make_disco_info(jid(), state()) -> disco_info().
|
||||
make_disco_info(_From, StateData) ->
|
||||
Config = StateData#state.config,
|
||||
Feats = [?NS_VCARD, ?NS_MUC,
|
||||
?CONFIG_OPT_TO_FEATURE((Config#config.public),
|
||||
@@ -3664,11 +3807,35 @@ process_iq_disco_info(_From, #iq{type = get, lang = Lang}, StateData) ->
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
{result, #disco_info{xdata = [iq_disco_info_extras(Lang, StateData)],
|
||||
identities = [#identity{category = <<"conference">>,
|
||||
type = <<"text">>,
|
||||
name = get_title(StateData)}],
|
||||
features = Feats}}.
|
||||
#disco_info{identities = [#identity{category = <<"conference">>,
|
||||
type = <<"text">>,
|
||||
name = get_title(StateData)}],
|
||||
features = Feats}.
|
||||
|
||||
-spec process_iq_disco_info(jid(), iq(), state()) ->
|
||||
{result, disco_info()} | {error, stanza_error()}.
|
||||
process_iq_disco_info(_From, #iq{type = set, lang = Lang}, _StateData) ->
|
||||
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||
{error, xmpp:err_not_allowed(Txt, Lang)};
|
||||
process_iq_disco_info(From, #iq{type = get, lang = Lang,
|
||||
sub_els = [#disco_info{node = <<>>}]},
|
||||
StateData) ->
|
||||
DiscoInfo = make_disco_info(From, StateData),
|
||||
Extras = iq_disco_info_extras(Lang, StateData),
|
||||
{result, DiscoInfo#disco_info{xdata = [Extras]}};
|
||||
process_iq_disco_info(From, #iq{type = get, lang = Lang,
|
||||
sub_els = [#disco_info{node = Node}]},
|
||||
StateData) ->
|
||||
try
|
||||
true = mod_caps:is_valid_node(Node),
|
||||
DiscoInfo = make_disco_info(From, StateData),
|
||||
Hash = mod_caps:compute_disco_hash(DiscoInfo, sha),
|
||||
Node = <<(?EJABBERD_URI)/binary, $#, Hash/binary>>,
|
||||
{result, DiscoInfo#disco_info{node = Node}}
|
||||
catch _:{badmatch, _} ->
|
||||
Txt = <<"Invalid node name">>,
|
||||
{error, xmpp:err_item_not_found(Txt, Lang)}
|
||||
end.
|
||||
|
||||
-spec iq_disco_info_extras(binary(), state()) -> xdata().
|
||||
iq_disco_info_extras(Lang, StateData) ->
|
||||
@@ -3734,13 +3901,15 @@ process_iq_vcard(_From, #iq{type = get}, StateData) ->
|
||||
{error, _} ->
|
||||
{error, xmpp:err_item_not_found()}
|
||||
end;
|
||||
process_iq_vcard(From, #iq{type = set, lang = Lang, sub_els = [SubEl]},
|
||||
process_iq_vcard(From, #iq{type = set, lang = Lang, sub_els = [Pkt]},
|
||||
StateData) ->
|
||||
case get_affiliation(From, StateData) of
|
||||
owner ->
|
||||
VCardRaw = fxml:element_to_binary(xmpp:encode(SubEl)),
|
||||
SubEl = xmpp:encode(Pkt),
|
||||
VCardRaw = fxml:element_to_binary(SubEl),
|
||||
Hash = mod_vcard_xupdate:compute_hash(SubEl),
|
||||
Config = StateData#state.config,
|
||||
NewConfig = Config#config{vcard = VCardRaw},
|
||||
NewConfig = Config#config{vcard = VCardRaw, vcard_xupdate = Hash},
|
||||
change_config(NewConfig, StateData);
|
||||
_ ->
|
||||
ErrText = <<"Owner privileges required">>,
|
||||
@@ -4162,6 +4331,28 @@ send_wrapped(From, To, Packet, Node, State) ->
|
||||
ok
|
||||
end;
|
||||
true ->
|
||||
case Packet of
|
||||
#presence{type = unavailable} ->
|
||||
case xmpp:get_subtag(Packet, #muc_user{}) of
|
||||
#muc_user{destroy = Destroy,
|
||||
status_codes = Codes} ->
|
||||
case Destroy /= undefined orelse
|
||||
(lists:member(110,Codes) andalso
|
||||
not lists:member(303, Codes)) of
|
||||
true ->
|
||||
ejabberd_router:route(
|
||||
#presence{from = State#state.jid, to = To,
|
||||
id = randoms:get_string(),
|
||||
type = unavailable});
|
||||
false ->
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
ejabberd_router:route(xmpp:set_from_to(Packet, From, To))
|
||||
end.
|
||||
|
||||
|
||||
+231
-263
@@ -47,52 +47,36 @@
|
||||
-include("translate.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-record(state,
|
||||
{lserver, lservice, access, service_limits}).
|
||||
-record(multicastc, {rserver :: binary(),
|
||||
response,
|
||||
ts :: integer()}).
|
||||
|
||||
-record(dest, {jid_string :: binary() | none,
|
||||
jid_jid :: xmpp:jid(),
|
||||
type :: to | cc | bcc,
|
||||
address :: address()}).
|
||||
|
||||
-type limit_value() :: {default | custom, integer()}.
|
||||
-record(limits, {message :: limit_value(),
|
||||
presence :: limit_value()}).
|
||||
|
||||
-record(service_limits, {local :: #limits{},
|
||||
remote :: #limits{}}).
|
||||
|
||||
-type routing() :: route_single | {route_multicast, binary(), #service_limits{}}.
|
||||
|
||||
-record(group, {server :: binary(),
|
||||
dests :: [#dest{}],
|
||||
multicast :: routing(),
|
||||
others :: [#address{}],
|
||||
addresses :: [#address{}]}).
|
||||
|
||||
-record(state, {lserver :: binary(),
|
||||
lservice :: binary(),
|
||||
access :: atom(),
|
||||
service_limits :: #service_limits{}}).
|
||||
-type state() :: #state{}.
|
||||
|
||||
-record(multicastc, {rserver, response, ts}).
|
||||
|
||||
%% ts: timestamp (in seconds) when the cache item was last updated
|
||||
|
||||
-record(dest, {jid_string = none :: binary(),
|
||||
jid_jid :: jid(),
|
||||
type :: atom(),
|
||||
full_xml :: address()}).
|
||||
|
||||
%% jid_string = string()
|
||||
%% jid_jid = jid()
|
||||
%% full_xml = xml()
|
||||
|
||||
-record(group,
|
||||
{server, dests, multicast, others, addresses}).
|
||||
|
||||
%% server = string()
|
||||
%% dests = [string()]
|
||||
%% multicast = {cached, local_server} | {cached, string()} | {cached, not_supported} | {obsolete, not_supported} | {obsolete, string()} | not_cached
|
||||
%% after being updated, possible values are: local | multicast_not_supported | {multicast_supported, string(), limits()}
|
||||
%% others = [xml()]
|
||||
%% packet = xml()
|
||||
|
||||
-record(waiter,
|
||||
{awaiting, group, renewal = false, sender, packet,
|
||||
aattrs, addresses}).
|
||||
|
||||
%% awaiting = {[Remote_service], Local_service, Type_awaiting}
|
||||
%% Remote_service = Local_service = string()
|
||||
%% Type_awaiting = info | items
|
||||
%% group = #group
|
||||
%% renewal = true | false
|
||||
%% sender = From
|
||||
%% packet = xml()
|
||||
%% aattrs = [xml()]
|
||||
|
||||
-record(limits, {message, presence}).
|
||||
|
||||
%% message = presence = integer() | infinite
|
||||
|
||||
-record(service_limits, {local, remote}).
|
||||
|
||||
%% All the elements are of type value()
|
||||
|
||||
-define(VERSION_MULTICAST, <<"$Revision: 440 $ ">>).
|
||||
@@ -104,6 +88,8 @@
|
||||
|
||||
-define(MAXTIME_CACHE_NEGATIVE, 86400).
|
||||
|
||||
-define(MAXTIME_CACHE_NEGOTIATING, 600).
|
||||
|
||||
-define(CACHE_PURGE_TIMER, 86400000).
|
||||
|
||||
-define(DISCO_QUERY_TIMEOUT, 10000).
|
||||
@@ -130,6 +116,7 @@ reload(LServerS, NewOpts, OldOpts) ->
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
|
||||
-spec init(list()) -> {ok, state()}.
|
||||
init([LServerS, Opts]) ->
|
||||
process_flag(trap_exit, true),
|
||||
[LServiceS|_] = gen_mod:get_opt_hosts(LServerS, Opts),
|
||||
@@ -137,7 +124,6 @@ init([LServerS, Opts]) ->
|
||||
SLimits = build_service_limit_record(gen_mod:get_opt(limits, Opts)),
|
||||
create_cache(),
|
||||
try_start_loop(),
|
||||
create_pool(),
|
||||
ejabberd_router_multicast:register_route(LServerS),
|
||||
ejabberd_router:register_route(LServiceS, LServerS),
|
||||
{ok,
|
||||
@@ -277,21 +263,22 @@ iq_vcard(Lang) ->
|
||||
%%% Route
|
||||
%%%-------------------------
|
||||
|
||||
-spec route_trusted(binary(), binary(), jid(), [jid()], stanza()) -> 'ok'.
|
||||
route_trusted(LServiceS, LServerS, FromJID,
|
||||
Destinations, Packet) ->
|
||||
Packet_stripped = Packet,
|
||||
AAttrs = [],
|
||||
Delivereds = [],
|
||||
Dests2 = lists:map(
|
||||
fun(D) ->
|
||||
#dest{jid_string = jid:encode(D),
|
||||
jid_jid = D, type = bcc,
|
||||
full_xml = #address{type = bcc, jid = D}}
|
||||
jid_jid = D, type = bcc,
|
||||
address = #address{type = bcc, jid = D}}
|
||||
end, Destinations),
|
||||
Groups = group_dests(Dests2),
|
||||
route_common(LServerS, LServiceS, FromJID, Groups,
|
||||
Delivereds, Packet_stripped, AAttrs).
|
||||
Delivereds, Packet_stripped).
|
||||
|
||||
-spec route_untrusted(binary(), binary(), atom(), #service_limits{}, stanza()) -> 'ok'.
|
||||
route_untrusted(LServiceS, LServerS, Access, SLimits, Packet) ->
|
||||
try route_untrusted2(LServiceS, LServerS, Access,
|
||||
SLimits, Packet)
|
||||
@@ -321,6 +308,7 @@ route_untrusted(LServiceS, LServerS, Access, SLimits, Packet) ->
|
||||
<<"Unknown problem">>)
|
||||
end.
|
||||
|
||||
-spec route_untrusted2(binary(), binary(), atom(), #service_limits{}, stanza()) -> 'ok'.
|
||||
route_untrusted2(LServiceS, LServerS, Access, SLimits, Packet) ->
|
||||
FromJID = xmpp:get_from(Packet),
|
||||
ok = check_access(LServerS, Access, FromJID),
|
||||
@@ -333,53 +321,40 @@ route_untrusted2(LServiceS, LServerS, Access, SLimits, Packet) ->
|
||||
Groups = group_dests(Dests2),
|
||||
ok = check_relay(FromJID#jid.server, LServerS, Groups),
|
||||
route_common(LServerS, LServiceS, FromJID, Groups,
|
||||
Delivereds, Packet_stripped, []).
|
||||
Delivereds, Packet_stripped).
|
||||
|
||||
-spec route_common(binary(), binary(), jid(), [#group{}],
|
||||
[address()], stanza(), list()) -> any().
|
||||
[address()], stanza()) -> 'ok'.
|
||||
route_common(LServerS, LServiceS, FromJID, Groups,
|
||||
Delivereds, Packet_stripped, AAttrs) ->
|
||||
Groups2 = look_cached_servers(LServerS, Groups),
|
||||
Delivereds, Packet_stripped) ->
|
||||
Groups2 = look_cached_servers(LServerS, LServiceS, Groups),
|
||||
Groups3 = build_others_xml(Groups2),
|
||||
Groups4 = add_addresses(Delivereds, Groups3),
|
||||
AGroups = decide_action_groups(Groups4),
|
||||
act_groups(FromJID, Packet_stripped, AAttrs, LServiceS,
|
||||
act_groups(FromJID, Packet_stripped, LServiceS,
|
||||
AGroups).
|
||||
|
||||
act_groups(FromJID, Packet_stripped, AAttrs, LServiceS,
|
||||
AGroups) ->
|
||||
[perform(FromJID, Packet_stripped, AAttrs, LServiceS,
|
||||
AGroup)
|
||||
|| AGroup <- AGroups].
|
||||
-spec act_groups(jid(), stanza(), binary(), [{routing(), #group{}}]) -> 'ok'.
|
||||
act_groups(FromJID, Packet_stripped, LServiceS, AGroups) ->
|
||||
lists:foreach(
|
||||
fun(AGroup) ->
|
||||
perform(FromJID, Packet_stripped, LServiceS,
|
||||
AGroup)
|
||||
end, AGroups).
|
||||
|
||||
perform(From, Packet, AAttrs, _,
|
||||
-spec perform(jid(), stanza(), binary(),
|
||||
{routing(), #group{}}) -> 'ok'.
|
||||
perform(From, Packet, _,
|
||||
{route_single, Group}) ->
|
||||
[route_packet(From, ToUser, Packet, AAttrs,
|
||||
Group#group.others, Group#group.addresses)
|
||||
|| ToUser <- Group#group.dests];
|
||||
perform(From, Packet, AAttrs, _,
|
||||
lists:foreach(
|
||||
fun(ToUser) ->
|
||||
route_packet(From, ToUser, Packet,
|
||||
Group#group.others, Group#group.addresses)
|
||||
end, Group#group.dests);
|
||||
perform(From, Packet, _,
|
||||
{{route_multicast, JID, RLimits}, Group}) ->
|
||||
route_packet_multicast(From, JID, Packet, AAttrs,
|
||||
Group#group.dests, Group#group.addresses, RLimits);
|
||||
perform(From, Packet, AAttrs, LServiceS,
|
||||
{{ask, Old_service, renewal}, Group}) ->
|
||||
send_query_info(Old_service, LServiceS),
|
||||
add_waiter(#waiter{awaiting =
|
||||
{[Old_service], LServiceS, info},
|
||||
group = Group, renewal = true, sender = From,
|
||||
packet = Packet, aattrs = AAttrs,
|
||||
addresses = Group#group.addresses});
|
||||
perform(_From, _Packet, _AAttrs, LServiceS,
|
||||
{{ask, LServiceS, _}, _Group}) ->
|
||||
ok;
|
||||
perform(From, Packet, AAttrs, LServiceS,
|
||||
{{ask, Server, not_renewal}, Group}) ->
|
||||
send_query_info(Server, LServiceS),
|
||||
add_waiter(#waiter{awaiting =
|
||||
{[Server], LServiceS, info},
|
||||
group = Group, renewal = false, sender = From,
|
||||
packet = Packet, aattrs = AAttrs,
|
||||
addresses = Group#group.addresses}).
|
||||
route_packet_multicast(From, JID, Packet,
|
||||
Group#group.dests, Group#group.addresses, RLimits).
|
||||
|
||||
%%%-------------------------
|
||||
%%% Check access permission
|
||||
@@ -427,7 +402,7 @@ split_addresses_todeliver(Addresses) ->
|
||||
%%% Check does not exceed limit of destinations
|
||||
%%%-------------------------
|
||||
|
||||
-spec check_limit_dests(_, jid(), stanza(), [address()]) -> ok.
|
||||
-spec check_limit_dests(#service_limits{}, jid(), stanza(), [address()]) -> ok.
|
||||
check_limit_dests(SLimits, FromJID, Packet,
|
||||
Addresses) ->
|
||||
SenderT = sender_type(FromJID),
|
||||
@@ -448,10 +423,10 @@ check_limit_dests(SLimits, FromJID, Packet,
|
||||
convert_dest_record(Addrs) ->
|
||||
lists:map(
|
||||
fun(#address{jid = undefined} = Addr) ->
|
||||
#dest{jid_string = none, full_xml = Addr};
|
||||
#dest{jid_string = none, address = Addr};
|
||||
(#address{jid = JID, type = Type} = Addr) ->
|
||||
#dest{jid_string = jid:encode(JID), jid_jid = JID,
|
||||
type = Type, full_xml = Addr}
|
||||
type = Type, address = Addr}
|
||||
end, Addrs).
|
||||
|
||||
%%%-------------------------
|
||||
@@ -469,9 +444,9 @@ split_dests_jid(Dests) ->
|
||||
end,
|
||||
Dests).
|
||||
|
||||
-spec report_not_jid(jid(), stanza(), #dest{}) -> any().
|
||||
-spec report_not_jid(jid(), stanza(), [#dest{}]) -> any().
|
||||
report_not_jid(From, Packet, Dests) ->
|
||||
Dests2 = [fxml:element_to_binary(xmpp:encode(Dest#dest.full_xml))
|
||||
Dests2 = [fxml:element_to_binary(xmpp:encode(Dest#dest.address))
|
||||
|| Dest <- Dests],
|
||||
[route_error(xmpp:set_from_to(Packet, From, From), jid_malformed,
|
||||
<<"This service can not process the address: ",
|
||||
@@ -497,14 +472,14 @@ group_dests(Dests) ->
|
||||
%%% Look for cached responses
|
||||
%%%-------------------------
|
||||
|
||||
look_cached_servers(LServerS, Groups) ->
|
||||
[look_cached(LServerS, Group) || Group <- Groups].
|
||||
look_cached_servers(LServerS, LServiceS, Groups) ->
|
||||
[look_cached(LServerS, LServiceS, Group) || Group <- Groups].
|
||||
|
||||
look_cached(LServerS, G) ->
|
||||
look_cached(LServerS, LServiceS, G) ->
|
||||
Maxtime_positive = (?MAXTIME_CACHE_POSITIVE),
|
||||
Maxtime_negative = (?MAXTIME_CACHE_NEGATIVE),
|
||||
Cached_response = search_server_on_cache(G#group.server,
|
||||
LServerS,
|
||||
LServerS, LServiceS,
|
||||
{Maxtime_positive,
|
||||
Maxtime_negative}),
|
||||
G#group{multicast = Cached_response}.
|
||||
@@ -520,7 +495,7 @@ build_others_xml(Groups) ->
|
||||
|
||||
build_other_xml(Dests) ->
|
||||
lists:foldl(fun (Dest, R) ->
|
||||
XML = Dest#dest.full_xml,
|
||||
XML = Dest#dest.address,
|
||||
case Dest#dest.type of
|
||||
to -> [add_delivered(XML) | R];
|
||||
cc -> [add_delivered(XML) | R];
|
||||
@@ -554,53 +529,38 @@ add_addresses2(Delivereds, [Group | Groups], Res, Pa,
|
||||
%%% Decide action groups
|
||||
%%%-------------------------
|
||||
|
||||
-spec decide_action_groups([#group{}]) -> [{routing(), #group{}}].
|
||||
decide_action_groups(Groups) ->
|
||||
[{decide_action_group(Group), Group}
|
||||
[{Group#group.multicast, Group}
|
||||
|| Group <- Groups].
|
||||
|
||||
decide_action_group(Group) ->
|
||||
Server = Group#group.server,
|
||||
case Group#group.multicast of
|
||||
{cached, local_server} ->
|
||||
%% Send a copy of the packet to each local user on Dests
|
||||
route_single;
|
||||
{cached, not_supported} ->
|
||||
%% Send a copy of the packet to each remote user on Dests
|
||||
route_single;
|
||||
{cached, {multicast_supported, JID, RLimits}} ->
|
||||
{route_multicast, JID, RLimits};
|
||||
{obsolete,
|
||||
{multicast_supported, Old_service, _RLimits}} ->
|
||||
{ask, Old_service, renewal};
|
||||
{obsolete, not_supported} -> {ask, Server, not_renewal};
|
||||
not_cached -> {ask, Server, not_renewal}
|
||||
end.
|
||||
|
||||
%%%-------------------------
|
||||
%%% Route packet
|
||||
%%%-------------------------
|
||||
|
||||
route_packet(From, ToDest, Packet, AAttrs, Others, Addresses) ->
|
||||
-spec route_packet(jid(), #dest{}, xmpp:stanza(), [addresses()], [addresses()]) -> 'ok'.
|
||||
route_packet(From, ToDest, Packet, Others, Addresses) ->
|
||||
Dests = case ToDest#dest.type of
|
||||
bcc -> [];
|
||||
_ -> [ToDest]
|
||||
end,
|
||||
route_packet2(From, ToDest#dest.jid_string, Dests,
|
||||
Packet, AAttrs, {Others, Addresses}).
|
||||
Packet, {Others, Addresses}).
|
||||
|
||||
route_packet_multicast(From, ToS, Packet, AAttrs, Dests,
|
||||
-spec route_packet_multicast(jid(), binary(), xmpp:stanza(), [#dest{}], [address()], #limits{}) -> 'ok'.
|
||||
route_packet_multicast(From, ToS, Packet, Dests,
|
||||
Addresses, Limits) ->
|
||||
Type_of_stanza = type_of_stanza(Packet),
|
||||
{_Type, Limit_number} = get_limit_number(Type_of_stanza,
|
||||
Limits),
|
||||
Fragmented_dests = fragment_dests(Dests, Limit_number),
|
||||
[route_packet2(From, ToS, DFragment, Packet, AAttrs,
|
||||
Addresses)
|
||||
|| DFragment <- Fragmented_dests].
|
||||
lists:foreach(fun(DFragment) ->
|
||||
route_packet2(From, ToS, DFragment, Packet,
|
||||
Addresses)
|
||||
end, Fragmented_dests).
|
||||
|
||||
-spec route_packet2(jid(), binary(), [#dest{}], stanza(), list(), [address()]) -> ok.
|
||||
route_packet2(From, ToS, Dests, Packet, _AAttrs,
|
||||
Addresses) ->
|
||||
-spec route_packet2(jid(), binary(), [#dest{}], xmpp:stanza(), {[address()], [address()]} | [address()]) -> 'ok'.
|
||||
route_packet2(From, ToS, Dests, Packet, Addresses) ->
|
||||
Els = case append_dests(Dests, Addresses) of
|
||||
[] ->
|
||||
xmpp:get_els(Packet);
|
||||
@@ -613,10 +573,10 @@ route_packet2(From, ToS, Dests, Packet, _AAttrs,
|
||||
|
||||
-spec append_dests([#dest{}], {[address()], [address()]} | [address()]) -> [address()].
|
||||
append_dests(_Dests, {Others, Addresses}) ->
|
||||
Addresses++Others;
|
||||
Addresses ++ Others;
|
||||
append_dests([], Addresses) -> Addresses;
|
||||
append_dests([Dest | Dests], Addresses) ->
|
||||
append_dests(Dests, [Dest#dest.full_xml | Addresses]).
|
||||
append_dests(Dests, [Dest#dest.address | Addresses]).
|
||||
|
||||
%%%-------------------------
|
||||
%%% Check relay
|
||||
@@ -647,20 +607,22 @@ check_relay_required(LServerS, Groups) ->
|
||||
%%% Check protocol support: Send request
|
||||
%%%-------------------------
|
||||
|
||||
send_query_info(RServerS, LServiceS) ->
|
||||
-spec send_query_info(binary(), binary(), binary()) -> ok.
|
||||
send_query_info(RServerS, LServiceS, ID) ->
|
||||
case str:str(RServerS, <<"echo.">>) of
|
||||
1 -> false;
|
||||
_ -> send_query(RServerS, LServiceS, #disco_info{})
|
||||
1 -> ok;
|
||||
_ -> send_query(RServerS, LServiceS, ID, #disco_info{})
|
||||
end.
|
||||
|
||||
send_query_items(RServerS, LServiceS) ->
|
||||
send_query(RServerS, LServiceS, #disco_items{}).
|
||||
-spec send_query_items(binary(), binary(), binary()) -> ok.
|
||||
send_query_items(RServerS, LServiceS, ID) ->
|
||||
send_query(RServerS, LServiceS, ID, #disco_items{}).
|
||||
|
||||
-spec send_query(binary(), binary(), [disco_info()|disco_items()]) -> ok.
|
||||
send_query(RServerS, LServiceS, SubEl) ->
|
||||
-spec send_query(binary(), binary(), binary(), disco_info()|disco_items()) -> ok.
|
||||
send_query(RServerS, LServiceS, ID, SubEl) ->
|
||||
Packet = #iq{from = stj(LServiceS),
|
||||
to = stj(RServerS),
|
||||
id = randoms:get_string(),
|
||||
id = ID,
|
||||
type = get, sub_els = [SubEl]},
|
||||
ejabberd_router:route(Packet).
|
||||
|
||||
@@ -670,10 +632,31 @@ send_query(RServerS, LServiceS, SubEl) ->
|
||||
|
||||
process_iqreply_error(LServiceS, Packet) ->
|
||||
FromS = jts(xmpp:get_from(Packet)),
|
||||
case search_waiter(FromS, LServiceS, info) of
|
||||
{found_waiter, Waiter} ->
|
||||
received_awaiter(FromS, Waiter, LServiceS);
|
||||
_ -> ok
|
||||
ID = Packet#iq.id,
|
||||
case str:tokens(ID, <<"/">>) of
|
||||
[RServer, _] ->
|
||||
case look_server(RServer) of
|
||||
{cached, {_Response, {wait_for_info, ID}}, _TS}
|
||||
when RServer == FromS ->
|
||||
add_response(RServer, not_supported, cached);
|
||||
{cached, {_Response, {wait_for_items, ID}}, _TS}
|
||||
when RServer == FromS ->
|
||||
add_response(RServer, not_supported, cached);
|
||||
{cached, {Response, {wait_for_items_info, ID, Items}},
|
||||
_TS} ->
|
||||
case lists:member(FromS, Items) of
|
||||
true ->
|
||||
received_awaiter(
|
||||
FromS, RServer, Response, ID, Items,
|
||||
LServiceS);
|
||||
false ->
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
%%%-------------------------
|
||||
@@ -681,12 +664,12 @@ process_iqreply_error(LServiceS, Packet) ->
|
||||
%%%-------------------------
|
||||
|
||||
-spec process_iqreply_result(binary(), iq()) -> any().
|
||||
process_iqreply_result(LServiceS, #iq{from = From, sub_els = [SubEl]}) ->
|
||||
process_iqreply_result(LServiceS, #iq{from = From, id = ID, sub_els = [SubEl]}) ->
|
||||
case SubEl of
|
||||
#disco_info{} ->
|
||||
process_discoinfo_result(From, LServiceS, SubEl);
|
||||
process_discoinfo_result(From, LServiceS, ID, SubEl);
|
||||
#disco_items{} ->
|
||||
process_discoitems_result(From, LServiceS, SubEl);
|
||||
process_discoitems_result(From, LServiceS, ID, SubEl);
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
@@ -695,46 +678,53 @@ process_iqreply_result(LServiceS, #iq{from = From, sub_els = [SubEl]}) ->
|
||||
%%% Check protocol support: Receive response: Disco Info
|
||||
%%%-------------------------
|
||||
|
||||
process_discoinfo_result(From, LServiceS, DiscoInfo) ->
|
||||
process_discoinfo_result(From, LServiceS, ID, DiscoInfo) ->
|
||||
FromS = jts(From),
|
||||
case search_waiter(FromS, LServiceS, info) of
|
||||
{found_waiter, Waiter} ->
|
||||
process_discoinfo_result2(From, FromS, LServiceS, DiscoInfo,
|
||||
Waiter);
|
||||
_ -> ok
|
||||
case str:tokens(ID, <<"/">>) of
|
||||
[RServer, _] ->
|
||||
case look_server(RServer) of
|
||||
{cached, {Response, {wait_for_info, ID} = ST}, _TS}
|
||||
when RServer == FromS ->
|
||||
process_discoinfo_result2(
|
||||
From, FromS, LServiceS, DiscoInfo,
|
||||
RServer, Response, ST);
|
||||
{cached, {Response, {wait_for_items_info, ID, Items} = ST},
|
||||
_TS} ->
|
||||
case lists:member(FromS, Items) of
|
||||
true ->
|
||||
process_discoinfo_result2(
|
||||
From, FromS, LServiceS, DiscoInfo,
|
||||
RServer, Response, ST);
|
||||
false ->
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
process_discoinfo_result2(From, FromS, LServiceS,
|
||||
#disco_info{features = Feats} = DiscoInfo,
|
||||
Waiter) ->
|
||||
RServer, Response, ST) ->
|
||||
Multicast_support = lists:member(?NS_ADDRESS, Feats),
|
||||
Group = Waiter#waiter.group,
|
||||
RServer = Group#group.server,
|
||||
case Multicast_support of
|
||||
true ->
|
||||
SenderT = sender_type(From),
|
||||
RLimits = get_limits_xml(DiscoInfo, SenderT),
|
||||
add_response(RServer, {multicast_supported, FromS, RLimits}),
|
||||
FromM = Waiter#waiter.sender,
|
||||
DestsM = Group#group.dests,
|
||||
PacketM = Waiter#waiter.packet,
|
||||
AAttrsM = Waiter#waiter.aattrs,
|
||||
AddressesM = Waiter#waiter.addresses,
|
||||
RServiceM = FromS,
|
||||
route_packet_multicast(FromM, RServiceM, PacketM,
|
||||
AAttrsM, DestsM, AddressesM, RLimits),
|
||||
delo_waiter(Waiter);
|
||||
add_response(RServer, {multicast_supported, FromS, RLimits}, cached);
|
||||
false ->
|
||||
case FromS of
|
||||
RServer ->
|
||||
send_query_items(FromS, LServiceS),
|
||||
delo_waiter(Waiter),
|
||||
add_waiter(Waiter#waiter{awaiting =
|
||||
{[FromS], LServiceS, items},
|
||||
renewal = false});
|
||||
%% We asked a component, and it does not support XEP33
|
||||
_ -> received_awaiter(FromS, Waiter, LServiceS)
|
||||
end
|
||||
case ST of
|
||||
{wait_for_info, _ID} ->
|
||||
Random = randoms:get_string(),
|
||||
ID = <<RServer/binary, $/, Random/binary>>,
|
||||
send_query_items(FromS, LServiceS, ID),
|
||||
add_response(RServer, Response, {wait_for_items, ID});
|
||||
%% We asked a component, and it does not support XEP33
|
||||
{wait_for_items_info, ID, Items} ->
|
||||
received_awaiter(FromS, RServer, Response, ID, Items, LServiceS)
|
||||
end
|
||||
end.
|
||||
|
||||
get_limits_xml(DiscoInfo, SenderT) ->
|
||||
@@ -778,26 +768,32 @@ get_limits_values(Fields) ->
|
||||
%%% Check protocol support: Receive response: Disco Items
|
||||
%%%-------------------------
|
||||
|
||||
process_discoitems_result(From, LServiceS, #disco_items{items = Items}) ->
|
||||
process_discoitems_result(From, LServiceS, ID, #disco_items{items = Items}) ->
|
||||
FromS = jts(From),
|
||||
case search_waiter(FromS, LServiceS, items) of
|
||||
{found_waiter, Waiter} ->
|
||||
List = lists:flatmap(
|
||||
fun(#disco_item{jid = #jid{luser = <<"">>,
|
||||
lresource = <<"">>} = J}) ->
|
||||
[J];
|
||||
(_) ->
|
||||
[]
|
||||
end, Items),
|
||||
case List of
|
||||
[] ->
|
||||
received_awaiter(FromS, Waiter, LServiceS);
|
||||
case str:tokens(ID, <<"/">>) of
|
||||
[FromS = RServer, _] ->
|
||||
case look_server(RServer) of
|
||||
{cached, {Response, {wait_for_items, ID}}, _TS} ->
|
||||
List = lists:flatmap(
|
||||
fun(#disco_item{jid = #jid{luser = <<"">>,
|
||||
lserver = LServer,
|
||||
lresource = <<"">>}}) ->
|
||||
[LServer];
|
||||
(_) ->
|
||||
[]
|
||||
end, Items),
|
||||
case List of
|
||||
[] ->
|
||||
add_response(RServer, not_supported, cached);
|
||||
_ ->
|
||||
Random = randoms:get_string(),
|
||||
ID2 = <<RServer/binary, $/, Random/binary>>,
|
||||
[send_query_info(Item, LServiceS, ID2) || Item <- List],
|
||||
add_response(RServer, Response,
|
||||
{wait_for_items_info, ID2, List})
|
||||
end;
|
||||
_ ->
|
||||
[send_query_info(Item, LServiceS) || Item <- List],
|
||||
delo_waiter(Waiter),
|
||||
add_waiter(Waiter#waiter{awaiting =
|
||||
{List, LServiceS, info},
|
||||
renewal = false})
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
@@ -807,33 +803,12 @@ process_discoitems_result(From, LServiceS, #disco_items{items = Items}) ->
|
||||
%%% Check protocol support: Receive response: Received awaiter
|
||||
%%%-------------------------
|
||||
|
||||
received_awaiter(JID, Waiter, LServiceS) ->
|
||||
{JIDs, LServiceS, _} = Waiter#waiter.awaiting,
|
||||
delo_waiter(Waiter),
|
||||
Group = Waiter#waiter.group,
|
||||
RServer = Group#group.server,
|
||||
received_awaiter(JID, RServer, Response, ID, JIDs, _LServiceS) ->
|
||||
case lists:delete(JID, JIDs) of
|
||||
[] ->
|
||||
case Waiter#waiter.renewal of
|
||||
false ->
|
||||
add_response(RServer, not_supported),
|
||||
From = Waiter#waiter.sender,
|
||||
Packet = Waiter#waiter.packet,
|
||||
AAttrs = Waiter#waiter.aattrs,
|
||||
Others = Group#group.others,
|
||||
Addresses = Waiter#waiter.addresses,
|
||||
[route_packet(From, ToUser, Packet, AAttrs, Others, Addresses)
|
||||
|| ToUser <- Group#group.dests];
|
||||
true ->
|
||||
send_query_info(RServer, LServiceS),
|
||||
add_waiter(Waiter#waiter{awaiting =
|
||||
{[RServer], LServiceS, info},
|
||||
renewal = false})
|
||||
end;
|
||||
JIDs2 ->
|
||||
add_waiter(Waiter#waiter{awaiting =
|
||||
{JIDs2, LServiceS, info},
|
||||
renewal = false})
|
||||
[] ->
|
||||
add_response(RServer, not_supported, cached);
|
||||
JIDs2 ->
|
||||
add_response(RServer, Response, {wait_for_items_info, ID, JIDs2})
|
||||
end.
|
||||
|
||||
%%%-------------------------
|
||||
@@ -845,25 +820,52 @@ create_cache() ->
|
||||
[{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, multicastc)}]).
|
||||
|
||||
add_response(RServer, Response) ->
|
||||
add_response(RServer, Response, State) ->
|
||||
Secs = calendar:datetime_to_gregorian_seconds(calendar:local_time()),
|
||||
mnesia:dirty_write(#multicastc{rserver = RServer,
|
||||
response = Response, ts = Secs}).
|
||||
response = {Response, State}, ts = Secs}).
|
||||
|
||||
search_server_on_cache(RServer, LServerS, _Maxmins)
|
||||
search_server_on_cache(RServer, LServerS, _LServiceS, _Maxmins)
|
||||
when RServer == LServerS ->
|
||||
{cached, local_server};
|
||||
search_server_on_cache(RServer, _LServerS, Maxmins) ->
|
||||
route_single;
|
||||
search_server_on_cache(RServer, _LServerS, LServiceS, Maxmins) ->
|
||||
case look_server(RServer) of
|
||||
not_cached -> not_cached;
|
||||
{cached, Response, Ts} ->
|
||||
Now = calendar:datetime_to_gregorian_seconds(calendar:local_time()),
|
||||
case is_obsolete(Response, Ts, Now, Maxmins) of
|
||||
false -> {cached, Response};
|
||||
true -> {obsolete, Response}
|
||||
end
|
||||
not_cached ->
|
||||
query_info(RServer, LServiceS, not_supported),
|
||||
route_single;
|
||||
{cached, {Response, State}, TS} ->
|
||||
Now = calendar:datetime_to_gregorian_seconds(calendar:local_time()),
|
||||
Response2 =
|
||||
case State of
|
||||
cached ->
|
||||
case is_obsolete(Response, TS, Now, Maxmins) of
|
||||
false -> ok;
|
||||
true ->
|
||||
query_info(RServer, LServiceS, Response)
|
||||
end,
|
||||
Response;
|
||||
_ ->
|
||||
if
|
||||
Now - TS > ?MAXTIME_CACHE_NEGOTIATING ->
|
||||
query_info(RServer, LServiceS, not_supported),
|
||||
not_supported;
|
||||
true ->
|
||||
Response
|
||||
end
|
||||
end,
|
||||
case Response2 of
|
||||
not_supported -> route_single;
|
||||
{multicast_supported, Service, Limits} ->
|
||||
{route_multicast, Service, Limits}
|
||||
end
|
||||
end.
|
||||
|
||||
query_info(RServer, LServiceS, Response) ->
|
||||
Random = randoms:get_string(),
|
||||
ID = <<RServer/binary, $/, Random/binary>>,
|
||||
send_query_info(RServer, LServiceS, ID),
|
||||
add_response(RServer, Response, {wait_for_info, ID}).
|
||||
|
||||
look_server(RServer) ->
|
||||
case mnesia:dirty_read(multicastc, RServer) of
|
||||
[] -> not_cached;
|
||||
@@ -934,44 +936,6 @@ purge_loop(NM) ->
|
||||
try_stop -> purge_loop_finished
|
||||
end.
|
||||
|
||||
%%%-------------------------
|
||||
%%% Pool
|
||||
%%%-------------------------
|
||||
|
||||
create_pool() ->
|
||||
catch
|
||||
begin
|
||||
ets:new(multicastp,
|
||||
[duplicate_bag, public, named_table, {keypos, 2}]),
|
||||
ets:give_away(multicastp, whereis(ejabberd), ok)
|
||||
end.
|
||||
|
||||
add_waiter(Waiter) ->
|
||||
true = ets:insert(multicastp, Waiter).
|
||||
|
||||
delo_waiter(Waiter) ->
|
||||
true = ets:delete_object(multicastp, Waiter).
|
||||
|
||||
-spec search_waiter(binary(), binary(), info | items) ->
|
||||
{found_waiter, #waiter{}} | waiter_not_found.
|
||||
|
||||
search_waiter(JID, LServiceS, Type) ->
|
||||
Rs = ets:foldl(fun (W, Res) ->
|
||||
{JIDs, LServiceS1, Type1} = W#waiter.awaiting,
|
||||
case lists:member(JID, JIDs) and
|
||||
(LServiceS == LServiceS1)
|
||||
and (Type1 == Type)
|
||||
of
|
||||
true -> Res ++ [W];
|
||||
false -> Res
|
||||
end
|
||||
end,
|
||||
[], multicastp),
|
||||
case Rs of
|
||||
[R | _] -> {found_waiter, R};
|
||||
[] -> waiter_not_found
|
||||
end.
|
||||
|
||||
%%%-------------------------
|
||||
%%% Limits: utils
|
||||
%%%-------------------------
|
||||
@@ -1005,11 +969,13 @@ get_from_limitopts(LimitOpts, SenderT) ->
|
||||
build_remote_limit_record(LimitOpts, SenderT) ->
|
||||
build_limit_record(LimitOpts, SenderT).
|
||||
|
||||
-spec build_limit_record(any(), local | remote) -> #limits{}.
|
||||
build_limit_record(LimitOpts, SenderT) ->
|
||||
Limits = [get_limit_value(Name, Default, LimitOpts)
|
||||
|| {Name, Default} <- list_of_limits(SenderT)],
|
||||
list_to_tuple([limits | Limits]).
|
||||
|
||||
-spec get_limit_value(atom(), integer(), any()) -> limit_value().
|
||||
get_limit_value(Name, Default, LimitOpts) ->
|
||||
case lists:keysearch(Name, 1, LimitOpts) of
|
||||
{value, {Name, Number}} -> {custom, Number};
|
||||
@@ -1018,11 +984,13 @@ get_limit_value(Name, Default, LimitOpts) ->
|
||||
|
||||
type_of_stanza(Stanza) -> element(1, Stanza).
|
||||
|
||||
-spec get_limit_number(message | presence, #limits{}) -> limit_value().
|
||||
get_limit_number(message, Limits) ->
|
||||
Limits#limits.message;
|
||||
get_limit_number(presence, Limits) ->
|
||||
Limits#limits.presence.
|
||||
|
||||
-spec get_slimit_group(local | remote, #service_limits{}) -> #limits{}.
|
||||
get_slimit_group(local, SLimits) ->
|
||||
SLimits#service_limits.local;
|
||||
get_slimit_group(remote, SLimits) ->
|
||||
|
||||
+51
-50
@@ -1704,7 +1704,7 @@ subscribe_node(Host, Node, From, JID, Configuration) ->
|
||||
Nidx = TNode#pubsub_node.id,
|
||||
Type = TNode#pubsub_node.type,
|
||||
Options = TNode#pubsub_node.options,
|
||||
send_items(Host, Node, Nidx, Type, Options, Subscriber, 1),
|
||||
send_items(Host, Node, Nidx, Type, Options, Subscriber, last),
|
||||
ServerHost = serverhost(Host),
|
||||
ejabberd_hooks:run(pubsub_subscribe_node, ServerHost,
|
||||
[ServerHost, Host, Node, Subscriber, SubId]),
|
||||
@@ -1954,7 +1954,7 @@ purge_node(Host, Node, Owner) ->
|
||||
end
|
||||
end,
|
||||
Reply = undefined,
|
||||
case transaction(Host, Node, Action, sync_dirty) of
|
||||
case transaction(Host, Node, Action, transaction) of
|
||||
{result, {TNode, {Result, broadcast}}} ->
|
||||
Nidx = TNode#pubsub_node.id,
|
||||
Type = TNode#pubsub_node.type,
|
||||
@@ -2004,10 +2004,11 @@ get_items(Host, Node, From, SubId, _MaxItems, ItemIds, RSM) ->
|
||||
Host, From, Owners, AccessModel, AllowedGroups),
|
||||
case ItemIds of
|
||||
[ItemId] ->
|
||||
NotFound = xmpp:err_item_not_found(),
|
||||
case node_call(Host, Type, get_item,
|
||||
[Nidx, ItemId, From, AccessModel, PS, RG, undefined])
|
||||
of
|
||||
{error, _} -> {result, {[], undefined}};
|
||||
{error, NotFound} -> {result, {[], undefined}};
|
||||
Result -> Result
|
||||
end;
|
||||
_ ->
|
||||
@@ -2017,7 +2018,7 @@ get_items(Host, Node, From, SubId, _MaxItems, ItemIds, RSM) ->
|
||||
end
|
||||
end,
|
||||
case transaction(Host, Node, Action, sync_dirty) of
|
||||
{result, {_, {Items, RsmOut}}} ->
|
||||
{result, {TNode, {Items, RsmOut}}} ->
|
||||
SendItems = case ItemIds of
|
||||
[] ->
|
||||
Items;
|
||||
@@ -2027,14 +2028,12 @@ get_items(Host, Node, From, SubId, _MaxItems, ItemIds, RSM) ->
|
||||
lists:member(ItemId, ItemIds)
|
||||
end, Items)
|
||||
end,
|
||||
{result,
|
||||
#pubsub{items = #ps_items{node = Node,
|
||||
items = itemsEls(SendItems)},
|
||||
rsm = RsmOut}};
|
||||
{result, {_, Item}} ->
|
||||
{result,
|
||||
#pubsub{items = #ps_items{node = Node,
|
||||
items = itemsEls([Item])}}};
|
||||
Options = TNode#pubsub_node.options,
|
||||
{result, #pubsub{items = items_els(Node, Options, SendItems),
|
||||
rsm = RsmOut}};
|
||||
{result, {TNode, Item}} ->
|
||||
Options = TNode#pubsub_node.options,
|
||||
{result, #pubsub{items = items_els(Node, Options, [Item])}};
|
||||
Error ->
|
||||
Error
|
||||
end.
|
||||
@@ -2068,6 +2067,9 @@ get_allowed_items_call(Host, Nidx, From, Type, Options, Owners, RSM) ->
|
||||
{PS, RG} = get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups),
|
||||
node_call(Host, Type, get_items, [Nidx, From, AccessModel, PS, RG, undefined, RSM]).
|
||||
|
||||
get_last_items(Host, Type, Nidx, LJID, last) ->
|
||||
% hack to handle section 6.1.7 of XEP-0060
|
||||
get_last_items(Host, Type, Nidx, LJID, 1);
|
||||
get_last_items(Host, Type, Nidx, LJID, 1) ->
|
||||
case get_cached_item(Host, Nidx) of
|
||||
undefined ->
|
||||
@@ -2634,45 +2636,37 @@ payload_xmlelements([#xmlel{} | Tail], Count) ->
|
||||
payload_xmlelements([_ | Tail], Count) ->
|
||||
payload_xmlelements(Tail, Count).
|
||||
|
||||
items_event_stanza(Node, Options, Items) ->
|
||||
MoreEls = case Items of
|
||||
[LastItem] ->
|
||||
{ModifNow, ModifUSR} = LastItem#pubsub_item.modification,
|
||||
[#delay{stamp = ModifNow, from = jid:make(ModifUSR)}];
|
||||
items_els(Node, Options, Items) ->
|
||||
Els = case get_option(Options, itemreply) of
|
||||
publisher ->
|
||||
[#ps_item{id = ItemId, sub_els = Payload, publisher = jid:encode(USR)}
|
||||
|| #pubsub_item{itemid = {ItemId, _}, payload = Payload, modification = {_, USR}}
|
||||
<- Items];
|
||||
_ ->
|
||||
[]
|
||||
[#ps_item{id = ItemId, sub_els = Payload}
|
||||
|| #pubsub_item{itemid = {ItemId, _}, payload = Payload}
|
||||
<- Items]
|
||||
end,
|
||||
BaseStanza = #message{
|
||||
sub_els = [#ps_event{items = #ps_items{
|
||||
node = Node,
|
||||
items = itemsEls(Items)}}
|
||||
| MoreEls]},
|
||||
NotificationType = get_option(Options, notification_type, headline),
|
||||
add_message_type(BaseStanza, NotificationType).
|
||||
#ps_items{node = Node, items = Els}.
|
||||
|
||||
%%%%%% broadcast functions
|
||||
|
||||
broadcast_publish_item(Host, Node, Nidx, Type, NodeOptions, ItemId, From, Payload, Removed) ->
|
||||
case get_collection_subscriptions(Host, Node) of
|
||||
SubsByDepth when is_list(SubsByDepth) ->
|
||||
EventItem0 = case get_option(NodeOptions, deliver_payloads) of
|
||||
true -> #ps_item{sub_els = Payload, id = ItemId};
|
||||
false -> #ps_item{id = ItemId}
|
||||
end,
|
||||
EventItem = case get_option(NodeOptions, itemreply, none) of
|
||||
owner -> %% owner not supported
|
||||
EventItem0;
|
||||
publisher ->
|
||||
EventItem0#ps_item{
|
||||
publisher = jid:encode(From)};
|
||||
none ->
|
||||
EventItem0
|
||||
end,
|
||||
Stanza = #message{
|
||||
sub_els =
|
||||
[#ps_event{items =
|
||||
#ps_items{node = Node,
|
||||
items = [EventItem]}}]},
|
||||
ItemPublisher = case get_option(NodeOptions, itemreply) of
|
||||
publisher -> jid:encode(From);
|
||||
_ -> <<>>
|
||||
end,
|
||||
ItemPayload = case get_option(NodeOptions, deliver_payloads) of
|
||||
true -> Payload;
|
||||
false -> []
|
||||
end,
|
||||
ItemsEls = #ps_items{node = Node,
|
||||
items = [#ps_item{id = ItemId,
|
||||
publisher = ItemPublisher,
|
||||
sub_els = ItemPayload}]},
|
||||
Stanza = #message{ sub_els = [#ps_event{items = ItemsEls}]},
|
||||
broadcast_stanza(Host, From, Node, Nidx, Type,
|
||||
NodeOptions, SubsByDepth, items, Stanza, true),
|
||||
case Removed of
|
||||
@@ -2899,8 +2893,20 @@ send_items(Host, Node, Nidx, Type, Options, Publisher, SubLJID, ToLJID, Number)
|
||||
[] ->
|
||||
ok;
|
||||
Items ->
|
||||
Stanza = items_event_stanza(Node, Options, Items),
|
||||
send_stanza(Publisher, ToLJID, Node, Stanza)
|
||||
Delay = case Number of
|
||||
last -> % handle section 6.1.7 of XEP-0060
|
||||
[Last] = Items,
|
||||
{Stamp, _USR} = Last#pubsub_item.modification,
|
||||
[#delay{stamp = Stamp}];
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
Stanza = #message{
|
||||
sub_els = [#ps_event{items = items_els(Node, Options, Items)}
|
||||
| Delay]},
|
||||
NotificationType = get_option(Options, notification_type, headline),
|
||||
send_stanza(Publisher, ToLJID, Node,
|
||||
add_message_type(Stanza, NotificationType))
|
||||
end.
|
||||
|
||||
send_stanza({LUser, LServer, _} = Publisher, USR, Node, BaseStanza) ->
|
||||
@@ -3732,11 +3738,6 @@ uniqid() ->
|
||||
{T1, T2, T3} = p1_time_compat:timestamp(),
|
||||
(str:format("~.16B~.16B~.16B", [T1, T2, T3])).
|
||||
|
||||
-spec itemsEls([#pubsub_item{}]) -> [ps_item()].
|
||||
itemsEls(Items) ->
|
||||
[#ps_item{id = ItemId, sub_els = Payload}
|
||||
|| #pubsub_item{itemid = {ItemId, _}, payload = Payload} <- Items].
|
||||
|
||||
-spec add_message_type(message(), message_type()) -> message().
|
||||
add_message_type(#message{} = Message, Type) ->
|
||||
Message#message{type = Type}.
|
||||
|
||||
+91
-16
@@ -44,7 +44,7 @@
|
||||
-export([get_commands_spec/0, delete_old_sessions/1]).
|
||||
|
||||
%% API (used by mod_push_keepalive).
|
||||
-export([notify/1, notify/3, notify/5]).
|
||||
-export([notify/2, notify/4, notify/6, is_message_with_body/1]).
|
||||
|
||||
%% For IQ callbacks
|
||||
-export([delete_session/3]).
|
||||
@@ -125,6 +125,12 @@ depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
-spec mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()].
|
||||
mod_opt_type(include_sender) ->
|
||||
fun (B) when is_boolean(B) -> B end;
|
||||
mod_opt_type(include_body) ->
|
||||
fun (B) when is_boolean(B) -> B;
|
||||
(S) -> iolist_to_binary(S)
|
||||
end;
|
||||
mod_opt_type(db_type) ->
|
||||
fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
mod_opt_type(O) when O == cache_life_time; O == cache_size ->
|
||||
@@ -134,8 +140,11 @@ mod_opt_type(O) when O == cache_life_time; O == cache_size ->
|
||||
mod_opt_type(O) when O == use_cache; O == cache_missed ->
|
||||
fun (B) when is_boolean(B) -> B end.
|
||||
|
||||
-spec mod_options(binary()) -> [{atom(), any()}].
|
||||
mod_options(Host) ->
|
||||
[{db_type, ejabberd_config:default_db(Host, ?MODULE)},
|
||||
[{include_sender, false},
|
||||
{include_body, false},
|
||||
{db_type, ejabberd_config:default_db(Host, ?MODULE)},
|
||||
{use_cache, ejabberd_config:use_cache(Host)},
|
||||
{cache_size, ejabberd_config:cache_size(Host)},
|
||||
{cache_missed, ejabberd_config:cache_missed(Host)},
|
||||
@@ -332,9 +341,12 @@ disable(#jid{luser = LUser, lserver = LServer, lresource = LResource} = JID,
|
||||
%% Hook callbacks.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec c2s_stanza(c2s_state(), xmpp_element() | xmlel(), term()) -> c2s_state().
|
||||
c2s_stanza(State, #stream_error{}, _SendResult) ->
|
||||
State;
|
||||
c2s_stanza(#{push_enabled := true, mgmt_state := pending} = State,
|
||||
_Pkt, _SendResult) ->
|
||||
notify(State),
|
||||
Pkt, _SendResult) ->
|
||||
?DEBUG("Notifying client of stanza", []),
|
||||
notify(State, Pkt),
|
||||
State;
|
||||
c2s_stanza(State, _Pkt, _SendResult) ->
|
||||
State.
|
||||
@@ -347,7 +359,7 @@ mam_message(#message{} = Pkt, LUser, LServer, _Peer, chat, _Dir) ->
|
||||
case drop_online_sessions(LUser, LServer, Clients) of
|
||||
[_|_] = Clients1 ->
|
||||
?DEBUG("Notifying ~s@~s of MAM message", [LUser, LServer]),
|
||||
notify(LUser, LServer, Clients1);
|
||||
notify(LUser, LServer, Clients1, Pkt);
|
||||
[] ->
|
||||
ok
|
||||
end;
|
||||
@@ -365,7 +377,7 @@ offline_message(#message{to = #jid{luser = LUser, lserver = LServer}} = Pkt) ->
|
||||
case lookup_sessions(LUser, LServer) of
|
||||
{ok, [_|_] = Clients} ->
|
||||
?DEBUG("Notifying ~s@~s of offline message", [LUser, LServer]),
|
||||
notify(LUser, LServer, Clients);
|
||||
notify(LUser, LServer, Clients, Pkt);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
@@ -375,8 +387,9 @@ offline_message(#message{to = #jid{luser = LUser, lserver = LServer}} = Pkt) ->
|
||||
c2s_session_pending(#{push_enabled := true, mgmt_queue := Queue} = State) ->
|
||||
case p1_queue:len(Queue) of
|
||||
Len when Len > 0 ->
|
||||
?DEBUG("Notifying client of unacknowledged messages", []),
|
||||
notify(State),
|
||||
?DEBUG("Notifying client of unacknowledged stanza(s)", []),
|
||||
Pkt = mod_stream_mgmt:queue_find(fun is_message_with_body/1, Queue),
|
||||
notify(State, Pkt),
|
||||
State;
|
||||
0 ->
|
||||
State
|
||||
@@ -408,17 +421,18 @@ remove_user(LUser, LServer) ->
|
||||
%%--------------------------------------------------------------------
|
||||
%% Generate push notifications.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec notify(c2s_state()) -> ok.
|
||||
notify(#{jid := #jid{luser = LUser, lserver = LServer}, sid := {TS, _}}) ->
|
||||
-spec notify(c2s_state(), xmpp_element() | xmlel() | none) -> ok.
|
||||
notify(#{jid := #jid{luser = LUser, lserver = LServer}, sid := {TS, _}}, Pkt) ->
|
||||
case lookup_session(LUser, LServer, TS) of
|
||||
{ok, Client} ->
|
||||
notify(LUser, LServer, [Client]);
|
||||
notify(LUser, LServer, [Client], Pkt);
|
||||
_Err ->
|
||||
ok
|
||||
end.
|
||||
|
||||
-spec notify(binary(), binary(), [push_session()]) -> ok.
|
||||
notify(LUser, LServer, Clients) ->
|
||||
-spec notify(binary(), binary(), [push_session()],
|
||||
xmpp_element() | xmlel() | none) -> ok.
|
||||
notify(LUser, LServer, Clients, Pkt) ->
|
||||
lists:foreach(
|
||||
fun({TS, PushLJID, Node, XData}) ->
|
||||
HandleResponse = fun(#iq{type = result}) ->
|
||||
@@ -429,14 +443,16 @@ notify(LUser, LServer, Clients) ->
|
||||
(timeout) ->
|
||||
ok % Hmm.
|
||||
end,
|
||||
notify(LServer, PushLJID, Node, XData, HandleResponse)
|
||||
notify(LServer, PushLJID, Node, XData, Pkt, HandleResponse)
|
||||
end, Clients).
|
||||
|
||||
-spec notify(binary(), ljid(), binary(), xdata(),
|
||||
xmpp_element() | xmlel() | none,
|
||||
fun((iq() | timeout) -> any())) -> ok.
|
||||
notify(LServer, PushLJID, Node, XData, HandleResponse) ->
|
||||
notify(LServer, PushLJID, Node, XData, Pkt, HandleResponse) ->
|
||||
From = jid:make(LServer),
|
||||
Item = #ps_item{sub_els = [#push_notification{}]},
|
||||
Summary = make_summary(LServer, Pkt),
|
||||
Item = #ps_item{sub_els = [#push_notification{xdata = Summary}]},
|
||||
PubSub = #pubsub{publish = #ps_publish{node = Node, items = [Item]},
|
||||
publish_options = XData},
|
||||
IQ = #iq{type = set,
|
||||
@@ -446,6 +462,15 @@ notify(LServer, PushLJID, Node, XData, HandleResponse) ->
|
||||
sub_els = [PubSub]},
|
||||
ejabberd_router:route_iq(IQ, HandleResponse).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Miscellaneous.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec is_message_with_body(stanza()) -> boolean().
|
||||
is_message_with_body(#message{} = Msg) ->
|
||||
get_body_text(Msg) /= none;
|
||||
is_message_with_body(_Stanza) ->
|
||||
false.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Internal functions.
|
||||
%%--------------------------------------------------------------------
|
||||
@@ -567,6 +592,56 @@ drop_online_sessions(LUser, LServer, Clients) ->
|
||||
[Client || {TS, _, _, _} = Client <- Clients,
|
||||
lists:keyfind(TS, 1, SessIDs) == false].
|
||||
|
||||
-spec make_summary(binary(), xmpp_element() | xmlel() | none)
|
||||
-> xdata() | undefined.
|
||||
make_summary(Host, #message{from = From} = Pkt) ->
|
||||
case {gen_mod:get_module_opt(Host, ?MODULE, include_sender),
|
||||
gen_mod:get_module_opt(Host, ?MODULE, include_body)} of
|
||||
{false, false} ->
|
||||
undefined;
|
||||
{IncludeSender, IncludeBody} ->
|
||||
case get_body_text(Pkt) of
|
||||
none ->
|
||||
undefined;
|
||||
Text ->
|
||||
Fields1 = case IncludeBody of
|
||||
StaticText when is_binary(StaticText) ->
|
||||
[{'last-message-body', StaticText}];
|
||||
true ->
|
||||
[{'last-message-body', Text}];
|
||||
false ->
|
||||
[]
|
||||
end,
|
||||
Fields2 = case IncludeSender of
|
||||
true ->
|
||||
[{'last-message-sender', From} | Fields1];
|
||||
false ->
|
||||
Fields1
|
||||
end,
|
||||
#xdata{type = submit, fields = push_summary:encode(Fields2)}
|
||||
end
|
||||
end;
|
||||
make_summary(_Host, _Pkt) ->
|
||||
undefined.
|
||||
|
||||
-spec get_body_text(message()) -> binary() | none.
|
||||
get_body_text(#message{body = Body} = Msg) ->
|
||||
case xmpp:get_text(Body) of
|
||||
Text when byte_size(Text) > 0 ->
|
||||
Text;
|
||||
<<>> ->
|
||||
case body_is_encrypted(Msg) of
|
||||
true ->
|
||||
<<"(encrypted)">>;
|
||||
false ->
|
||||
none
|
||||
end
|
||||
end.
|
||||
|
||||
-spec body_is_encrypted(message()) -> boolean().
|
||||
body_is_encrypted(#message{sub_els = SubEls}) ->
|
||||
lists:keyfind(<<"encrypted">>, #xmlel.name, SubEls) /= false.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Caching.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
@@ -130,18 +130,24 @@ unregister_hooks(Host) ->
|
||||
%%--------------------------------------------------------------------
|
||||
-spec c2s_stanza(c2s_state(), xmpp_element() | xmlel(), term()) -> c2s_state().
|
||||
c2s_stanza(#{push_enabled := true, mgmt_state := pending} = State,
|
||||
_Pkt, _SendResult) ->
|
||||
maybe_restore_resume_timeout(State);
|
||||
Pkt, _SendResult) ->
|
||||
case mod_push:is_message_with_body(Pkt) of
|
||||
true ->
|
||||
maybe_restore_resume_timeout(State);
|
||||
false ->
|
||||
State
|
||||
end;
|
||||
c2s_stanza(State, _Pkt, _SendResult) ->
|
||||
State.
|
||||
|
||||
-spec c2s_session_pending(c2s_state()) -> c2s_state().
|
||||
c2s_session_pending(#{push_enabled := true, mgmt_queue := Queue} = State) ->
|
||||
case p1_queue:len(Queue) of
|
||||
0 ->
|
||||
case mod_stream_mgmt:queue_find(fun mod_push:is_message_with_body/1,
|
||||
Queue) of
|
||||
none ->
|
||||
State1 = maybe_adjust_resume_timeout(State),
|
||||
maybe_start_wakeup_timer(State1);
|
||||
_ ->
|
||||
_Msg ->
|
||||
State
|
||||
end;
|
||||
c2s_session_pending(State) ->
|
||||
@@ -184,7 +190,7 @@ c2s_handle_cast(State, _Msg) ->
|
||||
c2s_handle_info(#{push_enabled := true, mgmt_state := pending,
|
||||
jid := JID} = State, {timeout, _, push_keepalive}) ->
|
||||
?INFO_MSG("Waking ~s before session times out", [jid:encode(JID)]),
|
||||
mod_push:notify(State),
|
||||
mod_push:notify(State, none),
|
||||
{stop, State};
|
||||
c2s_handle_info(State, _) ->
|
||||
State.
|
||||
@@ -229,7 +235,7 @@ wake_all(LServer) ->
|
||||
IgnoreResponse = fun(_) -> ok end,
|
||||
lists:foreach(fun({_, PushLJID, Node, XData}) ->
|
||||
mod_push:notify(LServer, PushLJID, Node,
|
||||
XData, IgnoreResponse)
|
||||
XData, none, IgnoreResponse)
|
||||
end, Sessions);
|
||||
error ->
|
||||
error
|
||||
|
||||
+3
-3
@@ -173,9 +173,9 @@ process_local_iq(#iq{type = set, from = From, lang = Lang,
|
||||
Txt = <<"Duplicated groups are not allowed by RFC6121">>,
|
||||
xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang));
|
||||
false ->
|
||||
#jid{server = Server} = From,
|
||||
Access = gen_mod:get_module_opt(Server, ?MODULE, access),
|
||||
case acl:match_rule(Server, Access, From) of
|
||||
#jid{lserver = LServer} = From,
|
||||
Access = gen_mod:get_module_opt(LServer, ?MODULE, access),
|
||||
case acl:match_rule(LServer, Access, From) of
|
||||
deny ->
|
||||
Txt = <<"Access denied by service policy">>,
|
||||
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
|
||||
|
||||
+18
-3
@@ -33,8 +33,8 @@
|
||||
c2s_unbinded_packet/2, c2s_closed/2, c2s_terminated/2,
|
||||
c2s_handle_send/3, c2s_handle_info/2, c2s_handle_call/3,
|
||||
c2s_handle_recv/3]).
|
||||
%% adjust pending session timeout
|
||||
-export([get_resume_timeout/1, set_resume_timeout/2]).
|
||||
%% adjust pending session timeout / access queue
|
||||
-export([get_resume_timeout/1, set_resume_timeout/2, queue_find/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("xmpp.hrl").
|
||||
@@ -304,7 +304,7 @@ c2s_terminated(State, _Reason) ->
|
||||
State.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Adjust pending session timeout
|
||||
%%% Adjust pending session timeout / access queue
|
||||
%%%===================================================================
|
||||
-spec get_resume_timeout(state()) -> non_neg_integer().
|
||||
get_resume_timeout(#{mgmt_timeout := Timeout}) ->
|
||||
@@ -317,6 +317,21 @@ set_resume_timeout(State, Timeout) ->
|
||||
State1 = restart_pending_timer(State, Timeout),
|
||||
State1#{mgmt_timeout => Timeout}.
|
||||
|
||||
-spec queue_find(fun((stanza()) -> boolean()), p1_queue:queue())
|
||||
-> stanza() | none.
|
||||
queue_find(Pred, Queue) ->
|
||||
case p1_queue:out(Queue) of
|
||||
{{value, {_, _, Pkt}}, Queue1} ->
|
||||
case Pred(Pkt) of
|
||||
true ->
|
||||
Pkt;
|
||||
false ->
|
||||
queue_find(Pred, Queue1)
|
||||
end;
|
||||
{empty, _Queue1} ->
|
||||
none
|
||||
end.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
|
||||
@@ -143,7 +143,7 @@ search_items(Entries, State) ->
|
||||
#eldap_entry{attributes = Attrs} = E, Attrs
|
||||
end,
|
||||
Entries),
|
||||
lists:flatmap(
|
||||
lists:filtermap(
|
||||
fun(Attrs) ->
|
||||
case eldap_utils:find_ldap_attrs(UIDs, Attrs) of
|
||||
{U, UIDAttrFormat} ->
|
||||
@@ -163,15 +163,15 @@ search_items(Entries, State) ->
|
||||
end,
|
||||
SearchReported),
|
||||
J = <<Username/binary, $@, LServer/binary>>,
|
||||
[{<<"jid">>, J} | RFields];
|
||||
{true, [{<<"jid">>, J} | RFields]};
|
||||
_ ->
|
||||
[]
|
||||
false
|
||||
end;
|
||||
_ ->
|
||||
[]
|
||||
false
|
||||
end;
|
||||
<<"">> ->
|
||||
[]
|
||||
false
|
||||
end
|
||||
end, Attributes).
|
||||
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
|
||||
-export([update_presence/1, vcard_set/1, remove_user/2,
|
||||
user_send_packet/1, mod_opt_type/1, mod_options/1, depends/2]).
|
||||
%% API
|
||||
-export([compute_hash/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
@@ -529,5 +529,4 @@ report_and_stop(Tab, Err) ->
|
||||
"Failed to convert '~s' table to binary: ~p",
|
||||
[Tab, Err])),
|
||||
?CRITICAL_MSG(ErrTxt, []),
|
||||
timer:sleep(1000),
|
||||
halt(string:substr(ErrTxt, 1, 199)).
|
||||
ejabberd:halt().
|
||||
|
||||
+58
-33
@@ -28,7 +28,7 @@
|
||||
-behaviour(ejabberd_config).
|
||||
|
||||
-export([start/1, stop/1, get/2, get/3, post/4, delete/2,
|
||||
put/4, patch/4, request/6, with_retry/4, opt_type/1]).
|
||||
put/4, patch/4, request/6, with_retry/4, opt_type/1]).
|
||||
|
||||
-include("logger.hrl").
|
||||
|
||||
@@ -36,9 +36,9 @@
|
||||
-define(CONNECT_TIMEOUT, 8000).
|
||||
|
||||
start(Host) ->
|
||||
p1_http:start(),
|
||||
Pool_size = ejabberd_config:get_option({ext_api_http_pool_size, Host}, 100),
|
||||
p1_http:set_pool_size(Pool_size).
|
||||
application:start(inets),
|
||||
Size = ejabberd_config:get_option({ext_api_http_pool_size, Host}, 100),
|
||||
httpc:set_options([{max_sessions, Size}]).
|
||||
|
||||
stop(_Host) ->
|
||||
ok.
|
||||
@@ -78,21 +78,26 @@ patch(Server, Path, Params, Content) ->
|
||||
request(Server, patch, Path, Params, "application/json", Data).
|
||||
|
||||
request(Server, Method, Path, Params, Mime, Data) ->
|
||||
URI = url(Server, Path, Params),
|
||||
URI = to_list(url(Server, Path, Params)),
|
||||
Opts = [{connect_timeout, ?CONNECT_TIMEOUT},
|
||||
{timeout, ?HTTP_TIMEOUT}],
|
||||
Hdrs = [{"connection", "keep-alive"},
|
||||
{"content-type", Mime},
|
||||
{"User-Agent", "ejabberd"}],
|
||||
{"User-Agent", "ejabberd"}],
|
||||
Req = if
|
||||
(Method =:= post) orelse (Method =:= patch) orelse (Method =:= put) orelse (Method =:= delete) ->
|
||||
{URI, Hdrs, to_list(Mime), Data};
|
||||
true ->
|
||||
{URI, Hdrs}
|
||||
end,
|
||||
Begin = os:timestamp(),
|
||||
Result = case catch p1_http:request(Method, URI, Hdrs, Data, Opts) of
|
||||
{ok, Code, _, <<>>} ->
|
||||
Result = try httpc:request(Method, Req, Opts, [{body_format, binary}]) of
|
||||
{ok, {{_, Code, _}, _, <<>>}} ->
|
||||
{ok, Code, []};
|
||||
{ok, Code, _, <<" ">>} ->
|
||||
{ok, {{_, Code, _}, _, <<" ">>}} ->
|
||||
{ok, Code, []};
|
||||
{ok, Code, _, <<"\r\n">>} ->
|
||||
{ok, {{_, Code, _}, _, <<"\r\n">>}} ->
|
||||
{ok, Code, []};
|
||||
{ok, Code, _, Body} ->
|
||||
{ok, {{_, Code, _}, _, Body}} ->
|
||||
try jiffy:decode(Body) of
|
||||
JSon ->
|
||||
{ok, Code, JSon}
|
||||
@@ -110,8 +115,9 @@ request(Server, Method, Path, Params, Mime, Data) ->
|
||||
"** URI = ~s~n"
|
||||
"** Err = ~p",
|
||||
[URI, Reason]),
|
||||
{error, {http_error, {error, Reason}}};
|
||||
{'EXIT', Reason} ->
|
||||
{error, {http_error, {error, Reason}}}
|
||||
catch
|
||||
exit:Reason ->
|
||||
?ERROR_MSG("HTTP request failed:~n"
|
||||
"** URI = ~s~n"
|
||||
"** Err = ~p",
|
||||
@@ -124,16 +130,16 @@ request(Server, Method, Path, Params, Mime, Data) ->
|
||||
End = os:timestamp(),
|
||||
Elapsed = timer:now_diff(End, Begin) div 1000, %% time in ms
|
||||
ejabberd_hooks:run(backend_api_response_time, Server,
|
||||
[Server, Method, Path, Elapsed]);
|
||||
[Server, Method, Path, Elapsed]);
|
||||
{error, {http_error,{error,timeout}}} ->
|
||||
ejabberd_hooks:run(backend_api_timeout, Server,
|
||||
[Server, Method, Path]);
|
||||
[Server, Method, Path]);
|
||||
{error, {http_error,{error,connect_timeout}}} ->
|
||||
ejabberd_hooks:run(backend_api_timeout, Server,
|
||||
[Server, Method, Path]);
|
||||
[Server, Method, Path]);
|
||||
{error, _} ->
|
||||
ejabberd_hooks:run(backend_api_error, Server,
|
||||
[Server, Method, Path])
|
||||
[Server, Method, Path])
|
||||
end,
|
||||
Result.
|
||||
|
||||
@@ -141,6 +147,11 @@ request(Server, Method, Path, Params, Mime, Data) ->
|
||||
%%% HTTP helpers
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
to_list(V) when is_binary(V) ->
|
||||
binary_to_list(V);
|
||||
to_list(V) ->
|
||||
V.
|
||||
|
||||
encode_json(Content) ->
|
||||
case catch jiffy:encode(Content) of
|
||||
{'EXIT', Reason} ->
|
||||
@@ -154,32 +165,46 @@ encode_json(Content) ->
|
||||
end.
|
||||
|
||||
base_url(Server, Path) ->
|
||||
Tail = case iolist_to_binary(Path) of
|
||||
BPath = case iolist_to_binary(Path) of
|
||||
<<$/, Ok/binary>> -> Ok;
|
||||
Ok -> Ok
|
||||
end,
|
||||
case Tail of
|
||||
<<"http", _Url/binary>> -> Tail;
|
||||
Url = case BPath of
|
||||
<<"http", _/binary>> -> BPath;
|
||||
_ ->
|
||||
Base = ejabberd_config:get_option({ext_api_url, Server},
|
||||
<<"http://localhost/api">>),
|
||||
<<Base/binary, "/", Tail/binary>>
|
||||
case binary:last(Base) of
|
||||
$/ -> <<Base/binary, BPath/binary>>;
|
||||
_ -> <<Base/binary, "/", BPath/binary>>
|
||||
end
|
||||
end,
|
||||
case binary:last(Url) of
|
||||
47 -> binary_part(Url, 0, size(Url)-1);
|
||||
_ -> Url
|
||||
end.
|
||||
|
||||
url(Server, Path, []) ->
|
||||
binary_to_list(base_url(Server, Path));
|
||||
url(Server, Path, Params) ->
|
||||
Base = base_url(Server, Path),
|
||||
[<<$&, ParHead/binary>> | ParTail] =
|
||||
[<<"&", (iolist_to_binary(Key))/binary, "=",
|
||||
(ejabberd_http:url_encode(Value))/binary>>
|
||||
url(Url, []) ->
|
||||
Url;
|
||||
url(Url, Params) ->
|
||||
L = [<<"&", (iolist_to_binary(Key))/binary, "=",
|
||||
(misc:url_encode(Value))/binary>>
|
||||
|| {Key, Value} <- Params],
|
||||
Tail = iolist_to_binary([ParHead | ParTail]),
|
||||
binary_to_list(<<Base/binary, $?, Tail/binary>>).
|
||||
<<$&, Encoded/binary>> = iolist_to_binary(L),
|
||||
<<Url/binary, $?, Encoded/binary>>.
|
||||
url(Server, Path, Params) ->
|
||||
case binary:split(base_url(Server, Path), <<"?">>) of
|
||||
[Url] ->
|
||||
url(Url, Params);
|
||||
[Url, Extra] ->
|
||||
Custom = [list_to_tuple(binary:split(P, <<"=">>))
|
||||
|| P <- binary:split(Extra, <<"&">>, [global])],
|
||||
url(Url, Custom++Params)
|
||||
end.
|
||||
|
||||
-spec opt_type(ext_api_http_pool_size) -> fun((pos_integer()) -> pos_integer());
|
||||
(ext_api_url) -> fun((binary()) -> binary());
|
||||
(atom()) -> [atom()].
|
||||
(ext_api_url) -> fun((binary()) -> binary());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(ext_api_http_pool_size) ->
|
||||
fun (X) when is_integer(X), X > 0 -> X end;
|
||||
opt_type(ext_api_url) ->
|
||||
|
||||
+4
-4
@@ -55,7 +55,7 @@ master_slave_cases() ->
|
||||
all_master(Config) ->
|
||||
Peer = ?config(peer, Config),
|
||||
Presence = #presence{to = Peer},
|
||||
ChatState = #message{to = Peer, thread = <<"1">>,
|
||||
ChatState = #message{to = Peer, thread = #message_thread{data = <<"1">>},
|
||||
sub_els = [#chatstate{type = active}]},
|
||||
Message = ChatState#message{body = [#text{data = <<"body">>}]},
|
||||
PepPayload = xmpp:encode(#presence{}),
|
||||
@@ -133,15 +133,15 @@ all_slave(Config) ->
|
||||
[#ps_item{
|
||||
id = <<"pep-2">>}]}},
|
||||
#delay{}]} = recv_message(Config),
|
||||
#message{from = Peer, thread = <<"1">>,
|
||||
#message{from = Peer, thread = #message_thread{data = <<"1">>},
|
||||
sub_els = [#chatstate{type = composing},
|
||||
#delay{}]} = recv_message(Config),
|
||||
#message{from = Peer, thread = <<"1">>,
|
||||
#message{from = Peer, thread = #message_thread{data = <<"1">>},
|
||||
body = [#text{data = <<"body">>}],
|
||||
sub_els = [#chatstate{type = active}]} = recv_message(Config),
|
||||
change_client_state(Config, active),
|
||||
wait_for_master(Config),
|
||||
#message{from = Peer, thread = <<"1">>,
|
||||
#message{from = Peer, thread = #message_thread{data = <<"1">>},
|
||||
sub_els = [#chatstate{type = active}]} = recv_message(Config),
|
||||
disconnect(Config).
|
||||
|
||||
|
||||
+14
-2
@@ -1083,9 +1083,21 @@ create_sql_tables(sqlite, _BaseDir) ->
|
||||
create_sql_tables(Type, BaseDir) ->
|
||||
{VHost, File} = case Type of
|
||||
mysql ->
|
||||
{?MYSQL_VHOST, "mysql.sql"};
|
||||
Path = case ejabberd_sql:use_new_schema() of
|
||||
true ->
|
||||
"mysql.new.sql";
|
||||
false ->
|
||||
"mysql.sql"
|
||||
end,
|
||||
{?MYSQL_VHOST, Path};
|
||||
pgsql ->
|
||||
{?PGSQL_VHOST, "pg.sql"}
|
||||
Path = case ejabberd_sql:use_new_schema() of
|
||||
true ->
|
||||
"pg.new.sql";
|
||||
false ->
|
||||
"pg.sql"
|
||||
end,
|
||||
{?PGSQL_VHOST, Path}
|
||||
end,
|
||||
SQLFile = filename:join([BaseDir, "sql", File]),
|
||||
CreationQueries = read_sql_queries(SQLFile),
|
||||
|
||||
+5
-16
@@ -268,22 +268,11 @@ muc_master(Config) ->
|
||||
recv_messages_from_room(Config, lists:seq(2, 21)),
|
||||
%% Now enable MAM for the conference
|
||||
%% Retrieve config first
|
||||
#iq{type = result, sub_els = [#muc_owner{config = #xdata{} = RoomCfg}]} =
|
||||
send_recv(Config, #iq{type = get, sub_els = [#muc_owner{}],
|
||||
to = Room}),
|
||||
%% Find the MAM field in the config and enable it
|
||||
NewFields = lists:flatmap(
|
||||
fun(#xdata_field{var = <<"mam">> = Var}) ->
|
||||
[#xdata_field{var = Var, values = [<<"1">>]}];
|
||||
(_) ->
|
||||
[]
|
||||
end, RoomCfg#xdata.fields),
|
||||
NewRoomCfg = #xdata{type = submit, fields = NewFields},
|
||||
#iq{type = result, sub_els = []} =
|
||||
send_recv(Config, #iq{type = set, to = Room,
|
||||
sub_els = [#muc_owner{config = NewRoomCfg}]}),
|
||||
#message{from = Room, type = groupchat,
|
||||
sub_els = [#muc_user{status_codes = [104]}]} = recv_message(Config),
|
||||
CfgOpts = muc_tests:get_config(Config),
|
||||
%% Find the MAM field in the config
|
||||
true = proplists:is_defined(mam, CfgOpts),
|
||||
%% Enable MAM
|
||||
[104] = muc_tests:set_config(Config, [{mam, true}]),
|
||||
%% Check if MAM has been enabled
|
||||
true = is_feature_advertised(Config, ?NS_MAM_1, Room),
|
||||
true = is_feature_advertised(Config, ?NS_MAM_2, Room),
|
||||
|
||||
+45
-25
@@ -800,6 +800,11 @@ change_affiliation_slave(Config, {Aff, Role, Status, Reason}) ->
|
||||
MyNick = ?config(nick, Config),
|
||||
MyNickJID = jid:replace_resource(Room, MyNick),
|
||||
ct:comment("Receiving affiliation change to ~s", [Aff]),
|
||||
if Aff == outcast ->
|
||||
#presence{from = Room, type = unavailable} = recv_presence(Config);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
#muc_user{status_codes = Codes,
|
||||
items = [#muc_item{role = Role,
|
||||
actor = Actor,
|
||||
@@ -858,6 +863,7 @@ kick_slave(Config) ->
|
||||
wait_for_master(Config),
|
||||
{[], _, _} = join(Config),
|
||||
ct:comment("Receiving role change to 'none'"),
|
||||
#presence{from = Room, type = unavailable} = recv_presence(Config),
|
||||
#muc_user{status_codes = Codes,
|
||||
items = [#muc_item{role = none,
|
||||
affiliation = none,
|
||||
@@ -889,6 +895,7 @@ destroy_master(Config) ->
|
||||
wait_for_slave(Config),
|
||||
ok = destroy(Config, Reason),
|
||||
ct:comment("Receiving destruction presence"),
|
||||
#presence{from = Room, type = unavailable} = recv_presence(Config),
|
||||
#muc_user{items = [#muc_item{role = none,
|
||||
affiliation = none}],
|
||||
destroy = #muc_destroy{jid = AltRoom,
|
||||
@@ -907,6 +914,7 @@ destroy_slave(Config) ->
|
||||
#stanza_error{reason = 'forbidden'} = destroy(Config, Reason),
|
||||
wait_for_master(Config),
|
||||
ct:comment("Receiving destruction presence"),
|
||||
#presence{from = Room, type = unavailable} = recv_presence(Config),
|
||||
#muc_user{items = [#muc_item{role = none,
|
||||
affiliation = none}],
|
||||
destroy = #muc_destroy{jid = AltRoom,
|
||||
@@ -938,6 +946,7 @@ vcard_master(Config) ->
|
||||
vcard_slave(Config) ->
|
||||
wait_for_master(Config),
|
||||
{[], _, _} = join(Config),
|
||||
[104] = recv_config_change_message(Config),
|
||||
VCard = get_event(Config),
|
||||
VCard = get_vcard(Config),
|
||||
#stanza_error{reason = 'forbidden'} = set_vcard(Config, VCard),
|
||||
@@ -1150,11 +1159,13 @@ config_members_only_master(Config) ->
|
||||
disconnect(Config).
|
||||
|
||||
config_members_only_slave(Config) ->
|
||||
Room = muc_room_jid(Config),
|
||||
MyJID = my_jid(Config),
|
||||
MyNickJID = my_muc_jid(Config),
|
||||
{[], _, _} = slave_join(Config),
|
||||
[104] = recv_config_change_message(Config),
|
||||
ct:comment("Getting kicked because the room has become members-only"),
|
||||
#presence{from = Room, type = unavailable} = recv_presence(Config),
|
||||
#muc_user{status_codes = Codes,
|
||||
items = [#muc_item{jid = MyJID,
|
||||
role = none,
|
||||
@@ -1171,6 +1182,7 @@ config_members_only_slave(Config) ->
|
||||
ct:comment("Waiting for the peer to ask for join"),
|
||||
join = get_event(Config),
|
||||
{[], _, _} = join(Config, participant, member),
|
||||
#presence{from = Room, type = unavailable} = recv_presence(Config),
|
||||
#muc_user{status_codes = NewCodes,
|
||||
items = [#muc_item{jid = MyJID,
|
||||
role = none,
|
||||
@@ -1555,8 +1567,9 @@ join_new(Config, Room) ->
|
||||
MyJID = my_jid(Config),
|
||||
MyNick = ?config(nick, Config),
|
||||
MyNickJID = jid:replace_resource(Room, MyNick),
|
||||
ct:comment("Joining new room"),
|
||||
ct:comment("Joining new room ~p", [Room]),
|
||||
send(Config, #presence{to = MyNickJID, sub_els = [#muc{}]}),
|
||||
#presence{from = Room, type = available} = recv_presence(Config),
|
||||
%% As per XEP-0045 we MUST receive stanzas in the following order:
|
||||
%% 1. In-room presence from other occupants
|
||||
%% 2. In-room presence from the joining entity itself (so-called "self-presence")
|
||||
@@ -1625,30 +1638,33 @@ join(Config, Role, Aff, SubEl) ->
|
||||
case recv_presence(Config) of
|
||||
#presence{type = error, from = MyNickJID} = Err ->
|
||||
xmpp:get_subtag(Err, #stanza_error{});
|
||||
#presence{type = available, from = PeerNickJID} = Pres ->
|
||||
#muc_user{items = [#muc_item{role = moderator,
|
||||
affiliation = owner}]} =
|
||||
xmpp:get_subtag(Pres, #muc_user{}),
|
||||
ct:comment("Receiving initial self-presence"),
|
||||
#muc_user{status_codes = Codes,
|
||||
items = [#muc_item{role = Role,
|
||||
jid = MyJID,
|
||||
affiliation = Aff}]} =
|
||||
recv_muc_presence(Config, MyNickJID, available),
|
||||
ct:comment("Checking if code '110' (self-presence) is set"),
|
||||
true = lists:member(110, Codes),
|
||||
{History, Subj} = recv_history_and_subject(Config),
|
||||
{History, Subj, Codes};
|
||||
#presence{type = available, from = MyNickJID} = Pres ->
|
||||
#muc_user{status_codes = Codes,
|
||||
items = [#muc_item{role = Role,
|
||||
jid = MyJID,
|
||||
affiliation = Aff}]} =
|
||||
xmpp:get_subtag(Pres, #muc_user{}),
|
||||
ct:comment("Checking if code '110' (self-presence) is set"),
|
||||
true = lists:member(110, Codes),
|
||||
{History, Subj} = recv_history_and_subject(Config),
|
||||
{empty, History, Subj, Codes}
|
||||
#presence{from = Room, type = available} ->
|
||||
case recv_presence(Config) of
|
||||
#presence{type = available, from = PeerNickJID} = Pres ->
|
||||
#muc_user{items = [#muc_item{role = moderator,
|
||||
affiliation = owner}]} =
|
||||
xmpp:get_subtag(Pres, #muc_user{}),
|
||||
ct:comment("Receiving initial self-presence"),
|
||||
#muc_user{status_codes = Codes,
|
||||
items = [#muc_item{role = Role,
|
||||
jid = MyJID,
|
||||
affiliation = Aff}]} =
|
||||
recv_muc_presence(Config, MyNickJID, available),
|
||||
ct:comment("Checking if code '110' (self-presence) is set"),
|
||||
true = lists:member(110, Codes),
|
||||
{History, Subj} = recv_history_and_subject(Config),
|
||||
{History, Subj, Codes};
|
||||
#presence{type = available, from = MyNickJID} = Pres ->
|
||||
#muc_user{status_codes = Codes,
|
||||
items = [#muc_item{role = Role,
|
||||
jid = MyJID,
|
||||
affiliation = Aff}]} =
|
||||
xmpp:get_subtag(Pres, #muc_user{}),
|
||||
ct:comment("Checking if code '110' (self-presence) is set"),
|
||||
true = lists:member(110, Codes),
|
||||
{History, Subj} = recv_history_and_subject(Config),
|
||||
{empty, History, Subj, Codes}
|
||||
end
|
||||
end.
|
||||
|
||||
leave(Config) ->
|
||||
@@ -1667,6 +1683,7 @@ leave(Config, Room) ->
|
||||
end,
|
||||
ct:comment("Leaving the room"),
|
||||
send(Config, #presence{to = MyNickJID, type = unavailable}),
|
||||
#presence{from = Room, type = unavailable} = recv_presence(Config),
|
||||
#muc_user{
|
||||
status_codes = Codes,
|
||||
items = [#muc_item{role = none, jid = MyJID}]} =
|
||||
@@ -1702,6 +1719,7 @@ set_config(Config, RoomConfig, Room) ->
|
||||
sub_els = [#muc_owner{config = #xdata{type = submit,
|
||||
fields = Fs}}]}) of
|
||||
#iq{type = result, sub_els = []} ->
|
||||
#presence{from = Room, type = available} = recv_presence(Config),
|
||||
#message{from = Room, type = groupchat} = Msg = recv_message(Config),
|
||||
#muc_user{status_codes = Codes} = xmpp:get_subtag(Msg, #muc_user{}),
|
||||
lists:sort(Codes);
|
||||
@@ -1846,6 +1864,7 @@ set_vcard(Config, VCard) ->
|
||||
case send_recv(Config, #iq{type = set, to = Room,
|
||||
sub_els = [VCard]}) of
|
||||
#iq{type = result, sub_els = []} ->
|
||||
[104] = recv_config_change_message(Config),
|
||||
ok;
|
||||
#iq{type = error} = Err ->
|
||||
xmpp:get_subtag(Err, #stanza_error{})
|
||||
@@ -1865,6 +1884,7 @@ get_vcard(Config) ->
|
||||
recv_config_change_message(Config) ->
|
||||
ct:comment("Receiving configuration change notification message"),
|
||||
Room = muc_room_jid(Config),
|
||||
#presence{from = Room, type = available} = recv_presence(Config),
|
||||
#message{type = groupchat, from = Room} = Msg = recv_message(Config),
|
||||
#muc_user{status_codes = Codes} = xmpp:get_subtag(Msg, #muc_user{}),
|
||||
lists:sort(Codes).
|
||||
|
||||
Reference in New Issue
Block a user