Compare commits

...

63 Commits

Author SHA1 Message Date
Christophe Romain 0bd4d1aade Update mix.exs version 2018-04-25 12:30:36 +02:00
Paweł Chmielowski d49aa429ca Update deps 2018-04-25 10:45:18 +02:00
Christophe Romain 316a19d600 Merge pull request #2399 from 4z3/ejabberdctl-fix-parser
ejabberdctl: fix parameters parsing
2018-04-25 10:39:19 +02:00
tv c43037887a ejabberdctl: fix parameter parsing 2018-04-25 00:41:30 +02:00
Holger Weiss 538e0d4844 misc: Catch all Base64 decoding errors 2018-04-24 18:29:10 +02:00
Holger Weiss f3795e9d03 mod_http_upload: Add MIME type for M4A files 2018-04-24 18:16:16 +02:00
Christophe Romain 3df919244c PubSub purge_node must use a transaction (#2231) 2018-04-24 15:58:56 +02:00
Christophe Romain 67773c5174 Merge branch 'master' of github.com:processone/ejabberd 2018-04-24 14:44:58 +02:00
Christophe Romain 61dee97738 Pubsub creation/modification use varchar (#2397) 2018-04-24 14:44:52 +02:00
Evgeniy Khramtsov 6774418a7f Introduce new mod_muc option: access_register
The option is an ACL rule defining who is able to register
nicknames within the conference service. The default is `all`
(for backward compatibility).
2018-04-24 12:29:59 +03:00
Evgeniy Khramtsov ad6fcc7865 Get rid of useless memory/disk usage warnings 2018-04-24 12:12:48 +03:00
Evgeniy Khramtsov ca28faa51a Fix get_affiliation/2 2018-04-24 12:07:10 +03:00
Paweł Chmielowski 5b730cdbf2 Use httpc directly instead of using p1_http wrapper 2018-04-23 17:40:44 +02:00
Paweł Chmielowski 9ed0357760 Use correct headers in rest calls 2018-04-23 12:29:56 +02:00
Evgeniy Khramtsov 06ce884aa8 Add stubs for affiliation-specific backend callbacks 2018-04-23 11:35:43 +03:00
Paweł Chmielowski 3fc0eb4f5b Use correct db backend for remove_mam_for_user_with_peer 2018-04-20 14:06:23 +02:00
Paweł Chmielowski 3bfa683586 Fix mnesia call in mam archive management function 2018-04-20 13:36:54 +02:00
Paweł Chmielowski 5be49cc0fa Add commands for cleaning up mam archive 2018-04-20 13:27:46 +02:00
Christophe Romain 42c029d5f7 Fix type of rest:url/2 2018-04-19 13:21:33 +02:00
Christophe Romain a567abcfdf Fix deprecated call injected by 265c7b62 2018-04-18 14:16:56 +02:00
Christophe Romain 265c7b62c7 Add flexibility on rest url config 2018-04-18 13:16:08 +02:00
Holger Weiss 332567693c mod_push_keepalive: Reset timeout on messages only
Some mobile apps might only be notified on actual chat messages with
a body, so don't let mod_push_keepalive reset the stream management
timeout on other types of traffic.
2018-04-17 00:27:07 +02:00
Holger Weiss de7dc4affa mod_push: Optionally include message sender/body
Add 'include_sender' and 'include_body' options.  If one or both of them
are set to 'true', a urn:xmpp:push:summary form with the enabled
field(s) is included in push notifications that are generated for
messages with a body.

The 'include_body' option can instead be set to a static text.  In this
case, the specified text will be included in place of the actual message
body.  This can be useful to signal the push service whether the
notification was triggered by a message with body (as opposed to other
types of traffic) without leaking actual message contents.
2018-04-16 23:18:03 +02:00
Holger Weiss 48c5ab59f1 mod_http_upload*: Remove empty lines after specs
Remove blank lines following function specifications in mod_http_upload
and mod_http_upload_quota for consistency with other modules.
2018-04-16 18:22:54 +02:00
Holger Weiss b2855d63a7 mod_http_upload*: Add function specifications 2018-04-16 18:17:28 +02:00
Holger Weiss 0282cf64a0 mod_push: Add function specification 2018-04-16 18:14:07 +02:00
Holger Weiss e5cb9dad40 mod_push: Add/adjust debug messages 2018-04-16 18:12:46 +02:00
Evgeniy Khramtsov ec819b4002 Update MUC MAM tests 2018-04-16 16:10:44 +03:00
Evgeniy Khramtsov acc162f4f4 Carefully validate options list 2018-04-16 15:48:06 +03:00
Evgeniy Khramtsov b8505f3e78 Don't crash on invalid module's sub-options
Fixes #2387
2018-04-16 11:06:57 +03:00
Holger Weiss 8a71e2e4f7 mod_push: Don't notify on stream errors
If a pending stream management session is closed with a stream error,
this is usually due to the client opening a new stream that conflicts
with the old one.  Don't generate a push notification in this situation.
2018-04-16 01:08:56 +02:00
Evgeniy Khramtsov a5284229cb Merge branch 'muc-self-presence' 2018-04-14 18:32:12 +03:00
Evgeniy Khramtsov d0f36537fb Clear fast_tls cache on configuration reload 2018-04-13 11:10:20 +03:00
Holger Weiss 3cf4fbc7b0 mod_roster: Use 'lserver' for configuration lookup 2018-04-13 00:12:07 +02:00
Paweł Chmielowski fe4b1a492c Fix notification payload generated by pubsub 2018-04-12 18:02:32 +02:00
Paweł Chmielowski c3b4b4ce4f Pass access option from websocket to c2s
This fixes issue #2223
2018-04-12 17:42:59 +02:00
Paweł Chmielowski 95244c3b6f Fix csi tests 2018-04-12 17:08:27 +02:00
a-iv 89d91b609a New schema support for tests. (#2355) 2018-04-12 15:42:43 +02:00
Christophe Romain d28064518b Improve pubsub#itemreply implementation (#2325) 2018-04-12 15:38:12 +02:00
Evgeniy Khramtsov 7627575856 Update the xmpp dependency to support 'parent' attribute
Fixes #2375
2018-04-11 09:34:06 +03:00
Christophe Romain 99444f2d0e Fix illegal match on previous commit 2018-04-10 15:02:03 +02:00
Christophe Romain 4c0f87b2ff Improve fix for #2288, don't mask errors on get_item 2018-04-10 14:47:18 +02:00
Holger Weiss 54363f8476 gen_mod: Support global module processes 2018-04-04 18:25:19 +02:00
Holger Weiss 094f586811 gen_mod: Remove frontend process support
ejabberd doesn't support frontend processes anymore.
2018-04-04 18:22:59 +02:00
Paweł Chmielowski 45a3c7e0ce Improve mod_multicast 2018-04-04 12:06:35 +02:00
Holger Weiss e2652ce02f mod_http_upload: Accept characters of any script
Accept all alphanumeric characters of any script in user and file names
rather than replacing non-ASCII characters with underscores.  However,
non-alphanumeric characters are still replaced, except for "." and "-".

Closes #2346.
2018-04-03 21:00:15 +02:00
Holger Weiss df651d893e Remove old hex conversion functions
Depend on list_to_integer/2 and integer_to_list/2 being available.
2018-04-03 00:21:33 +02:00
Holger Weiss a2e1f5c882 Move ejabberd_http:url_encode/1 to 'misc' module 2018-04-03 00:12:43 +02:00
Badlop 7f5796fe31 Fix Code format when logging a MUC room kick/ban 2018-04-02 13:51:19 +02:00
Holger Weiss 5f1191b9f5 mod_client_state: Add 'csi_activity' hook
Closes #2358.
2018-04-01 17:13:04 +02:00
Evgeny Khramtsov 0041a11c4a Merge pull request #2357 from Pouriya-Jahanbakhsh/component-send-packet-hook
feat: add hook for sending packet from component
2018-03-30 21:51:03 +03:00
Pouriya Jahanbakhsh e17a16a300 fix: run 'component_send_packet' hook in global mode 2018-03-30 23:19:33 +04:30
Pouriya Jahanbakhsh 7b3d26992b feat: add hook for sending packet from component
New hook 'component_send_packet' added.
Callback function must accept one argument {Pkt, ComponentState} and should yield 'drop' or {NewPkt, NewComponentState}.
2018-03-30 21:31:30 +04:30
Evgeniy Khramtsov 9373ad20ca Don't produce a crash dump during intentional exit
Also halt faster without relying on timeouts for buffers flushing
2018-03-29 12:14:31 +03:00
Evgeniy Khramtsov b283cfa6f2 Remove unused variable 2018-03-29 10:34:09 +03:00
Evgeny Khramtsov a84771fd14 Merge pull request #2351 from rom1dep/config-order_c2s-direct
config: move section about direct-tls for c2s just under regular c2s config
2018-03-29 08:32:13 +03:00
Romain DEP. 2bb6782bee config: move section about direct-tls for c2s just under regular c2s config (to ease parameters comparison) 2018-03-28 23:17:43 +02:00
Mickael Remond ae151927ae Add support for PATCH http method
This is needed to improve out APIs.
2018-03-28 17:34:47 +02:00
Paweł Chmielowski dfbdffad44 Fix process_discoitems_result in mod_multicast 2018-03-28 11:23:28 +02:00
Paweł Chmielowski d71bc73271 Update eimp 2018-03-26 16:18:29 +02:00
Evgeniy Khramtsov ea9c3fd8f7 Fix returning value from mod_vcard_ldap's search() callback
Fixes #2335
2018-03-25 10:53:46 +03:00
Evgeniy Khramtsov 63dba3fd64 Merge branch 'master' into muc-self-presence 2018-03-03 21:09:27 +03:00
Evgeniy Khramtsov ffe02c46e4 Let a MUC room to route presences from its bare JID
The goal for this is to provide entity capabilities (XEP-0115) and
vCard-based avatar hash (XEP-0153)
2018-02-12 17:37:36 +03:00
49 changed files with 1142 additions and 784 deletions
+14 -14
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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{}.
+1
View File
@@ -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()
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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.
+1 -2
View File
@@ -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}.
+1 -2
View File
@@ -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
View File
@@ -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;
+1
View File
@@ -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,
+1
View File
@@ -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) ->
+8 -2
View File
@@ -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),
+1 -15
View File
@@ -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}.
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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.
+17 -7
View File
@@ -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
View File
@@ -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) ->
+2 -18
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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.
%%--------------------------------------------------------------------
+13 -7
View File
@@ -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
View File
@@ -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
View File
@@ -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
%%%===================================================================
+5 -5
View File
@@ -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).
+2
View File
@@ -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").
+1 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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).