aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ejabberd_auth.erl5
-rw-r--r--src/ejabberd_auth_mnesia.erl29
-rw-r--r--src/ejabberd_auth_riak.erl4
-rw-r--r--src/ejabberd_auth_sql.erl26
-rw-r--r--src/ejabberd_http.erl13
-rw-r--r--src/ejabberd_oauth.erl123
-rw-r--r--src/ejabberd_web_admin.erl320
-rw-r--r--src/ejd2sql.erl15
-rw-r--r--src/misc.erl59
-rw-r--r--src/mod_bosh.erl64
-rw-r--r--src/mod_client_state.erl29
-rw-r--r--src/mod_http_fileserver.erl53
-rw-r--r--src/mod_mam_mnesia.erl4
-rw-r--r--src/mod_muc_log.erl277
-rw-r--r--src/mod_muc_room.erl38
-rw-r--r--src/mod_pubsub.erl38
-rw-r--r--src/mod_push.erl99
-rw-r--r--src/mod_push_mnesia.erl77
-rw-r--r--src/mod_push_sql.erl221
-rw-r--r--src/mod_register_web.erl41
-rw-r--r--src/mod_s2s_dialback.erl8
-rw-r--r--src/node_flat.erl24
-rw-r--r--src/nodetree_tree_sql.erl2
-rw-r--r--src/xmpp_stream_in.erl10
-rw-r--r--src/xmpp_stream_out.erl151
25 files changed, 747 insertions, 983 deletions
diff --git a/src/ejabberd_auth.erl b/src/ejabberd_auth.erl
index b34925ff0..4ab82a4e3 100644
--- a/src/ejabberd_auth.erl
+++ b/src/ejabberd_auth.erl
@@ -35,7 +35,7 @@
check_password/6, check_password_with_authmodule/4,
check_password_with_authmodule/6, try_register/3,
get_users/0, get_users/1, password_to_scram/1,
- get_users/2, export/1, import_info/0,
+ get_users/2, import_info/0,
count_users/1, import/5, import_start/2,
count_users/2, get_password/2,
get_password_s/2, get_password_with_authmodule/2,
@@ -798,9 +798,6 @@ validate_credentials(User, Server, Password) ->
end
end.
-export(Server) ->
- ejabberd_auth_mnesia:export(Server).
-
import_info() ->
[{<<"users">>, 3}].
diff --git a/src/ejabberd_auth_mnesia.erl b/src/ejabberd_auth_mnesia.erl
index 690152674..14b8e5a2d 100644
--- a/src/ejabberd_auth_mnesia.erl
+++ b/src/ejabberd_auth_mnesia.erl
@@ -34,16 +34,14 @@
-export([start/1, stop/1, set_password/3, try_register/3,
get_users/2, init_db/0,
count_users/2, get_password/2,
- remove_user/2, store_type/1, export/1, import/2,
+ remove_user/2, store_type/1, import/2,
plain_password_required/1, use_cache/1]).
-export([need_transform/1, transform/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
-include("ejabberd_sql_pt.hrl").
-
--record(passwd, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1',
- password = <<"">> :: binary() | scram() | '_'}).
+-include("ejabberd_auth.hrl").
-record(reg_users_counter, {vhost = <<"">> :: binary(),
count = 0 :: integer() | '$1'}).
@@ -272,29 +270,6 @@ transform(#passwd{password = Password} = P)
when is_record(Password, scram) ->
P.
-export(_Server) ->
- [{passwd,
- fun(Host, #passwd{us = {LUser, LServer}, password = Password})
- when LServer == Host,
- is_binary(Password) ->
- [?SQL("delete from users where username=%(LUser)s;"),
- ?SQL("insert into users(username, password) "
- "values (%(LUser)s, %(Password)s);")];
- (Host, #passwd{us = {LUser, LServer}, password = #scram{} = Scram})
- when LServer == Host ->
- StoredKey = Scram#scram.storedkey,
- ServerKey = Scram#scram.serverkey,
- Salt = Scram#scram.salt,
- IterationCount = Scram#scram.iterationcount,
- [?SQL("delete from users where username=%(LUser)s;"),
- ?SQL("insert into users(username, password, serverkey, salt, "
- "iterationcount) "
- "values (%(LUser)s, %(StoredKey)s, %(ServerKey)s,"
- " %(Salt)s, %(IterationCount)d);")];
- (_Host, _R) ->
- []
- end}].
-
import(LServer, [LUser, Password, _TimeStamp]) ->
mnesia:dirty_write(
#passwd{us = {LUser, LServer}, password = Password}).
diff --git a/src/ejabberd_auth_riak.erl b/src/ejabberd_auth_riak.erl
index fccaba102..37bd3daf4 100644
--- a/src/ejabberd_auth_riak.erl
+++ b/src/ejabberd_auth_riak.erl
@@ -40,9 +40,7 @@
-include("ejabberd.hrl").
-include("ejabberd_sql_pt.hrl").
-
--record(passwd, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1',
- password = <<"">> :: binary() | scram() | '_'}).
+-include("ejabberd_auth.hrl").
start(_Host) ->
ok.
diff --git a/src/ejabberd_auth_sql.erl b/src/ejabberd_auth_sql.erl
index 0d7c7b375..15e5076c9 100644
--- a/src/ejabberd_auth_sql.erl
+++ b/src/ejabberd_auth_sql.erl
@@ -35,11 +35,12 @@
-export([start/1, stop/1, set_password/3, try_register/3,
get_users/2, count_users/2, get_password/2,
remove_user/2, store_type/1, plain_password_required/1,
- convert_to_scram/1, opt_type/1]).
+ convert_to_scram/1, opt_type/1, export/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
-include("ejabberd_sql_pt.hrl").
+-include("ejabberd_auth.hrl").
-define(SALT_LENGTH, 16).
@@ -288,6 +289,29 @@ convert_to_scram(Server) ->
end
end.
+export(_Server) ->
+ [{passwd,
+ fun(Host, #passwd{us = {LUser, LServer}, password = Password})
+ when LServer == Host,
+ is_binary(Password) ->
+ [?SQL("delete from users where username=%(LUser)s;"),
+ ?SQL("insert into users(username, password) "
+ "values (%(LUser)s, %(Password)s);")];
+ (Host, #passwd{us = {LUser, LServer}, password = #scram{} = Scram})
+ when LServer == Host ->
+ StoredKey = Scram#scram.storedkey,
+ ServerKey = Scram#scram.serverkey,
+ Salt = Scram#scram.salt,
+ IterationCount = Scram#scram.iterationcount,
+ [?SQL("delete from users where username=%(LUser)s;"),
+ ?SQL("insert into users(username, password, serverkey, salt, "
+ "iterationcount) "
+ "values (%(LUser)s, %(StoredKey)s, %(ServerKey)s,"
+ " %(Salt)s, %(IterationCount)d);")];
+ (_Host, _R) ->
+ []
+ end}].
+
-spec opt_type(pgsql_users_number_estimate) -> fun((boolean()) -> boolean());
(atom()) -> [atom()].
opt_type(pgsql_users_number_estimate) ->
diff --git a/src/ejabberd_http.erl b/src/ejabberd_http.erl
index 43bbb3f04..30cce1952 100644
--- a/src/ejabberd_http.erl
+++ b/src/ejabberd_http.erl
@@ -266,10 +266,11 @@ process_header(State, Data) ->
add_header(Name, Value, State)};
{ok, http_eoh}
when State#state.request_host == undefined ->
- ?WARNING_MSG("An HTTP request without 'Host' HTTP "
- "header was received.",
- []),
- throw(http_request_no_host_header);
+ ?DEBUG("An HTTP request without 'Host' HTTP "
+ "header was received.", []),
+ {State1, Out} = process_request(State),
+ send_text(State1, Out),
+ process_header(State, {ok, {http_error, <<>>}});
{ok, http_eoh} ->
?DEBUG("(~w) http query: ~w ~p~n",
[State#state.socket, State#state.request_method,
@@ -418,6 +419,10 @@ extract_path_query(#state{request_method = Method,
extract_path_query(State) ->
{State, false}.
+process_request(#state{request_host = undefined,
+ custom_headers = CustomHeaders} = State) ->
+ {State, make_text_output(State, 400, CustomHeaders,
+ <<"Missing Host header">>)};
process_request(#state{request_method = Method,
request_auth = Auth,
request_lang = Lang,
diff --git a/src/ejabberd_oauth.erl b/src/ejabberd_oauth.erl
index def4b225a..df4e4bc21 100644
--- a/src/ejabberd_oauth.erl
+++ b/src/ejabberd_oauth.erl
@@ -632,120 +632,19 @@ web_head() ->
].
css() ->
- <<"
- body {
- margin: 0;
- padding: 0;
-
- font-family: sans-serif;
- color: #fff;
- }
-
- h1 {
- font-size: 3em;
- color: #444;
- }
-
- p {
- line-height: 1.5em;
- color: #888;
- }
-
- a {
- color: #fff;
- }
- a:hover,
- a:active {
- text-decoration: underline;
- }
-
- em {
- display: inline-block;
- padding: 0 5px;
-
- background: #f4f4f4;
- border-radius: 5px;
-
- font-style: normal;
- font-weight: bold;
- color: #444;
- }
-
- form {
- color: #444;
- }
- label {
- display: block;
- font-weight: bold;
- }
-
- input[type=text],
- input[type=password] {
- margin-bottom: 1em;
- padding: 0.4em;
-
- max-width: 330px;
- width: 100%;
-
- border: 1px solid #c4c4c4;
- border-radius: 5px;
- outline: 0;
-
- font-size: 1.2em;
- }
- input[type=text]:focus,
- input[type=password]:focus,
- input[type=text]:active,
- input[type=password]:active {
- border-color: #41AFCA;
- }
-
- input[type=submit] {
- font-size: 1em;
- }
-
- .container {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
-
- background: #424A55;
- background-image: -webkit-linear-gradient(270deg, rgba(48,52,62,0) 24%, #30353e 100%);
- background-image: linear-gradient(-180deg, rgba(48,52,62,0) 24%, #30353e 100%);
- }
-
- .section {
- padding: 3em;
- }
- .white.section {
- background: #fff;
- border-bottom: 4px solid #41AFCA;
- }
-
- .white.section a {
- text-decoration: none;
- color: #41AFCA;
- }
- .white.section a:hover,
- .white.section a:active {
- text-decoration: underline;
- }
-
- .container > .section {
- background: #424A55;
- }
-
- .block {
- margin: 0 auto;
- max-width: 900px;
- width: 100%;
- }
-">>.
+ case misc:read_css("oauth.css") of
+ {ok, Data} -> Data;
+ {error, _} -> <<>>
+ end.
logo() ->
- <<"data:image/png;base64,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">>.
+ case misc:read_img("oauth-logo.png") of
+ {ok, Img} ->
+ B64Img = base64:encode(Img),
+ <<"data:image/png;base64,", B64Img/binary>>;
+ {error, _} ->
+ <<>>
+ end.
-spec opt_type(oauth_expire) -> fun((non_neg_integer()) -> non_neg_integer());
(oauth_access) -> fun((any()) -> any());
diff --git a/src/ejabberd_web_admin.erl b/src/ejabberd_web_admin.erl
index 464ea6bfd..b3d72c19b 100644
--- a/src/ejabberd_web_admin.erl
+++ b/src/ejabberd_web_admin.erl
@@ -372,311 +372,37 @@ get_base_path(Host, Node) ->
%%%% css & images
additions_js() ->
- <<"\nfunction selectAll() {\n for(i=0;i<documen"
- "t.forms[0].elements.length;i++)\n { "
- "var e = document.forms[0].elements[i];\n "
- " if(e.type == 'checkbox')\n { e.checked "
- "= true; }\n }\n}\nfunction unSelectAll() "
- "{\n for(i=0;i<document.forms[0].elements.len"
- "gth;i++)\n { var e = document.forms[0].eleme"
- "nts[i];\n if(e.type == 'checkbox')\n "
- " { e.checked = false; }\n }\n}\n">>.
+ case misc:read_js("admin.js") of
+ {ok, JS} -> JS;
+ {error, _} -> <<>>
+ end.
css(Host) ->
- Base = get_base_path(Host, cluster),
- <<"html,body {\n"
- " margin: 0;\n"
- " padding: 0;\n"
- " height: 100%;\n"
- " background: #f9f9f9;\n"
- " font-family: sans-serif;\n"
- "}\n"
- "body {\n"
- " min-width: 990px;\n"
- "}\n"
- "a {\n"
- " text-decoration: none;\n"
- " color: #3eaffa;\n"
- "}\n"
- "a:hover,\n"
- "a:active {\n"
- " text-decoration: underline;\n"
- "}\n"
- "#container {\n"
- " position: relative;\n"
- " padding: 0;\n"
- " margin: 0 auto;\n"
- " max-width: 1280px;\n"
- " min-height: 100%;\n"
- " height: 100%;\n"
- " margin-bottom: -30px;\n"
- " z-index: 1;\n"
- "}\n"
- "html>body #container {\n"
- " height: auto;\n"
- "}\n"
- "#header h1 {\n"
- " width: 100%;\n"
- " height: 50px;\n"
- " padding: 0;\n"
- " margin: 0;\n"
- " background-color: #49cbc1;\n"
- "}\n"
- "#header h1 a {\n"
- " position: absolute;\n"
- " top: 0;\n"
- " left: 0;\n"
- " width: 100%;\n"
- " height: 50px;\n"
- " padding: 0;\n"
- " margin: 0;\n"
- " background: url('",Base/binary,"logo.png') 10px center no-repeat transparent;\n"
- " background-size: auto 25px;\n"
- " display: block;\n"
- " text-indent: -9999px;\n"
- "}\n"
- "#clearcopyright {\n"
- " display: block;\n"
- " width: 100%;\n"
- " height: 30px;\n"
- "}\n"
- "#copyrightouter {\n"
- " position: relative;\n"
- " display: table;\n"
- " width: 100%;\n"
- " height: 30px;\n"
- " z-index: 2;\n"
- "}\n"
- "#copyright {\n"
- " display: table-cell;\n"
- " vertical-align: bottom;\n"
- " width: 100%;\n"
- " height: 30px;\n"
- "}\n"
- "#copyright a {\n"
- " font-weight: bold;\n"
- " color: #fff;\n"
- "}\n"
- "#copyright p {\n"
- " margin-left: 0;\n"
- " margin-right: 0;\n"
- " margin-top: 5px;\n"
- " margin-bottom: 0;\n"
- " padding-left: 0;\n"
- " padding-right: 0;\n"
- " padding-top: 5px;\n"
- " padding-bottom: 5px;\n"
- " width: 100%;\n"
- " color: #fff;\n"
- " background-color: #30353E;\n"
- " font-size: 0.75em;\n"
- " text-align: center;\n"
- "}\n"
- "#navigation {\n"
- " display: inline-block;\n"
- " vertical-align: top;\n"
- " width: 30%;\n"
- "}\n"
- "#navigation ul {\n"
- " padding: 0;\n"
- " margin: 0;\n"
- " width: 90%;\n"
- " background: #fff;\n"
- "}\n"
- "#navigation ul li {\n"
- " list-style: none;\n"
- " margin: 0;\n"
- "\n"
- " border-bottom: 1px solid #f9f9f9;\n"
- " text-align: left;\n"
- "}\n"
- "#navigation ul li a {\n"
- " margin: 0;\n"
- " display: inline-block;\n"
- " padding: 10px;\n"
- " color: #333;\n"
- "}\n"
- "ul li #navhead a, ul li #navheadsub a, ul li #navheadsubsub a {\n"
- " font-size: 1.5em;\n"
- " color: inherit;\n"
- "}\n"
- "#navitemsub {\n"
- " border-left: 0.5em solid #424a55;\n"
- "}\n"
- "#navitemsubsub {\n"
- " border-left: 2em solid #424a55;\n"
- "}\n"
- "#navheadsub,\n"
- "#navheadsubsub {\n"
- " padding-left: 0.5em;\n"
- "}\n"
- "#navhead,\n"
- "#navheadsub,\n"
- "#navheadsubsub {\n"
- " border-top: 3px solid #49cbc1;\n"
- " background: #424a55;\n"
- " color: #fff;\n"
- "}\n"
- "#lastactivity li {\n"
- " padding: 2px;\n"
- " margin-bottom: -1px;\n"
- "}\n"
- "thead tr td {\n"
- " background: #3eaffa;\n"
- " color: #fff;\n"
- "}\n"
- "thead tr td a {\n"
- " color: #fff;\n"
- "}\n"
- "td.copy {\n"
- " text-align: center;\n"
- "}\n"
- "tr.head {\n"
- " color: #fff;\n"
- " background-color: #3b547a;\n"
- " text-align: center;\n"
- "}\n"
- "tr.oddraw {\n"
- " color: #412c75;\n"
- " background-color: #ccd4df;\n"
- " text-align: center;\n"
- "}\n"
- "tr.evenraw {\n"
- " color: #412c75;\n"
- " background-color: #dbe0e8;\n"
- " text-align: center;\n"
- "}\n"
- "td.leftheader {\n"
- " color: #412c75;\n"
- " background-color: #ccccc1;\n"
- " padding-left: 5px;\n"
- " padding-top: 2px;\n"
- " padding-bottom: 2px;\n"
- " margin-top: 0px;\n"
- " margin-bottom: 0px;\n"
- "}\n"
- "td.leftcontent {\n"
- " color: #000044;\n"
- " background-color: #e6e6df;\n"
- " padding-left: 5px;\n"
- " padding-right: 5px;\n"
- " padding-top: 2px;\n"
- " padding-bottom: 2px;\n"
- " margin-top: 0px;\n"
- " margin-bottom: 0px;\n"
- "}\n"
- "td.rightcontent {\n"
- " color: #000044;\n"
- " text-align: justify;\n"
- " padding-left: 10px;\n"
- " padding-right: 10px;\n"
- " padding-bottom: 5px;\n"
- "}\n"
- "\n"
- "h1 {\n"
- " color: #000044;\n"
- " padding-top: 2px;\n"
- " padding-bottom: 2px;\n"
- " margin-top: 0px;\n"
- " margin-bottom: 0px;\n"
- "}\n"
- "h2 {\n"
- " color: #000044;\n"
- " text-align: center;\n"
- " padding-top: 2px;\n"
- " padding-bottom: 2px;\n"
- " margin-top: 0px;\n"
- " margin-bottom: 0px;\n"
- "}\n"
- "h3 {\n"
- " color: #000044;\n"
- " text-align: left;\n"
- " padding-top: 20px;\n"
- " padding-bottom: 2px;\n"
- " margin-top: 0px;\n"
- " margin-bottom: 0px;\n"
- "}\n"
- "#content ul {\n"
- " padding-left: 1.1em;\n"
- " margin-top: 1em;\n"
- "}\n"
- "#content ul li {\n"
- " list-style-type: disc;\n"
- " padding: 5px;\n"
- "}\n"
- "#content ul.nolistyle>li {\n"
- " list-style-type: none;\n"
- "}\n"
- "#content {\n"
- " display: inline-block;\n"
- " vertical-align: top;\n"
- " padding-top: 25px;\n"
- " width: 70%;\n"
- "}\n"
- "div.guidelink,\n"
- "p[dir=ltr] {\n"
- " display: inline-block;\n"
- " float: right;\n"
- "\n"
- " margin: 0;\n"
- " margin-right: 1em;\n"
- "}\n"
- "div.guidelink a,\n"
- "p[dir=ltr] a {\n"
- " display: inline-block;\n"
- " border-radius: 3px;\n"
- " padding: 3px;\n"
- "\n"
- " background: #3eaffa;\n"
- "\n"
- " text-transform: uppercase;\n"
- " font-size: 0.75em;\n"
- " color: #fff;\n"
- "}\n"
- "table {\n"
- " margin-top: 1em;\n"
- "}\n"
- "table tr td {\n"
- " padding: 0.5em;\n"
- "}\n"
- "table tr:nth-child(odd) {\n"
- " background: #fff;\n"
- "}\n"
- "table.withtextareas>tbody>tr>td {\n"
- " vertical-align: top;\n"
- "}\n"
- "textarea {\n"
- " margin-bottom: 1em;\n"
- "}\n"
- "input,\n"
- "select {\n"
- " font-size: 1em;\n"
- "}\n"
- "p.result {\n"
- " border: 1px;\n"
- " border-style: dashed;\n"
- " border-color: #FE8A02;\n"
- " padding: 1em;\n"
- " margin-right: 1em;\n"
- " background: #FFE3C9;\n"
- "}\n"
- "*.alignright {\n"
- " text-align: right;\n"
- "}">>.
+ case misc:read_css("admin.css") of
+ {ok, CSS} ->
+ Base = get_base_path(Host, cluster),
+ re:replace(CSS, <<"@BASE@">>, Base, [{return, binary}]);
+ {error, _} ->
+ <<>>
+ end.
favicon() ->
- base64:decode(<<"AAABAAEAEBAAAAEAIAAoBQAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAA1AwMAQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMARQUEA+oFAwCOBAQAaAQEAGkEBABpBAQAaQQEAGoFAgBcBAAAOQAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAMDAEIHBgX/BwYF/wcGBf8HBgX/BwYF/wcGBf8HBgX/BwYF/wUFA/wEBAHOBQICXgAAAAAAAAAAAAAAAAAAAAADAwBCBwYF/wcGBf8HBgX/BwYF/wcGBf8HBgX/BwYF/wcGBf8HBgX/BwYF/wcGBf8DAwCUAAAABwAAAAAAAAAAAwMAQgcGBf8HBgX/BwYF/wcGBf8FBQPMBAAAaAQAAD8DAwNOAwMDlgUFA/QHBgX/BwYF/wQEAHkAAAAAAAAAAAMDAEIHBgX/BwYF/wcGBf8EBAGeAAAACAAAAAAAAAASAAAABQAAAAAFBQGxBwYF/wcGBf8FBAPvAAAAKAAAAAADAwBCBwYF/wcGBf8EBAHPAAAADQAAACEFBQGuBQQD8AUEAeEFBQGuBQQB9QcGBf8HBgX/BwYF/wQEAH8AAAAAAwMAQgcGBf8HBgX/BgQAbwAAAAADAwOXBQQB3gUFAdgFBQHZBQQB3QUFAdYFBAHhBQUD/gcGBf8EBAK8AAAAAAMDAEIHBgX/BwYF/wQAAD0AAAAAAAAABQAAAAEAAAABAAAAAQAAAAEAAAAFAAAAEQUFArwKBgX/BQMDxQAAAAADAwBCBwYF/wcGBf8DAwBKAAAAAwYDAFAGAwBVBgMAVAYDAFQFAgJZAAAALwAAAAAFBQGuCgYF/wUDA8QAAAAAAAAAKwUEA/QHBgX/AwMDlgAAAAAFAwOIBwYF/wcGBf8HBgX/BQQB5wAAADMAAAAWBQUD5wcGBf8EBAGbAAAAAAAAAAYFBAG9BwYF/wUFA/EDAABAAAAAAAMDA1QDAwOYBQUAhQAAACQAAAAABAQBnQcGBf8HBgX/AwMATQAAAAAAAAAAAwAAQwUFA/oHBgX/BQQB5QYDA1UAAAAAAAAAAAAAAAAAAAAXAwMAlwcGBf8HBgX/BQUBtQAAAAcAAAAAAAAAAAAAAAAEBABzBQUD/gcGBf8HBgX/BQMDyQQEAZwGBAGqBQQB5AcGBf8HBgX/BAQB0QAAACEAAAAAAAAAAAAAAAAAAAAAAAAAAAUFAmQFBAHlBwYF/wcGBf8HBgX/BwYF/wcGBf8FBQP+BQUBsAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHwUFA40FBAHrBwYF/wUFA/4FAwPGBgMAUgAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==">>).
+ case misc:read_img("favicon.png") of
+ {ok, ICO} -> ICO;
+ {error, _} -> <<>>
+ end.
logo() ->
- base64:decode(<<"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V4NgkhURIAJEgAgQASJQGgSWwNI7QYsRqG4ojdU0lAgQASJABIgAESACRIAIEAEiQAR0EYiiaAwobdmGC/8RdIyuluROBIgAESACRIAIEAEiQASIABEgAqVFAEFnmsB1J677Bmh8aYGj4USACBABIkAEiAARIAJEgAgQASJgB4EUgesduGayHe0ohQgQASJABIgAESACRIAIEAEiQARKj0CCwPUl1D2j9IARACJABIgAESACRIAIEAEiQASIABGwi0DMwPWfUW+4Xc0ojQgQASJABIgAESACRIAIEAEiQASIABAYIHDdhfMXEigiQASIABEgAkSACBABIkAEiAARIAK5IdAkcF2Hc+/NTTEKJgJEgAgQASJABIgAESACRIAIEAEiIAg0CFxX4fgsIkQEiAARIAJEgAgQASJABIgAESACRCB3BOoErqtx7IjcFaMCRIAIEAEiQASIABEgAkSACBABIkAEBIE+gesWfJ9NZIgAESACRIAIEAEiQASIABEgAkSACDiDQI/AdT/+/4AzilERIkAEiAARIAJEgAgQASJABIgAESACgkCPwPXviQgRIAJEgAgQASJABIgAESACRIAIEAHnEKgGrk/hc5BzylEhIkAEiAARIAJEgAgQASJABIgAESACCFhHgo4lEkSACBABIkAEiAARIAJEgAgQASJABIgAESACRIAIEAFjCPx/2P3JeG4VmJoAAAAASUVORK5CYII=">>).
+ case misc:read_img("admin-logo.png") of
+ {ok, Img} -> Img;
+ {error, _} -> <<>>
+ end.
logo_fill() ->
- base64:decode(<<"iVBORw0KGgoAAAANSUhEUgAAAAYAAAA3BAMAAADdxCZzA"
- "AAAAXNSR0IArs4c6QAAAB5QTFRF1nYO/ooC/o4O/pIS/p"
- "4q/q5K/rpq/sqM/tam/ubGzn/S/AAAAEFJREFUCNdlw0s"
- "RwCAQBUE+gSRHLGABC1jAAhbWAhZwC+88XdXOXb4UlFAr"
- "SmwN5ekdJY2BkudEec1QvrVQ/r3xOlK9HsTvertmAAAAA"
- "ElFTkSuQmCC">>).
+ case misc:read_img("admin-logo-fill.png") of
+ {ok, Img} -> Img;
+ {error, _} -> <<>>
+ end.
%%%==================================
%%%% process_admin
diff --git a/src/ejd2sql.erl b/src/ejd2sql.erl
index c801eb973..79533421e 100644
--- a/src/ejd2sql.erl
+++ b/src/ejd2sql.erl
@@ -59,6 +59,7 @@ modules() ->
mod_privacy,
mod_private,
mod_pubsub,
+ mod_push,
mod_roster,
mod_shared_roster,
mod_vcard].
@@ -73,18 +74,28 @@ export(Server, Output) ->
end, Modules),
close_output(Output, IO).
-export(Server, Output, Module) ->
+export(Server, Output, Module1) ->
+ Module = case Module1 of
+ mod_pubsub -> pubsub_db;
+ _ -> Module1
+ end,
+ SQLMod = gen_mod:db_mod(sql, Module),
LServer = jid:nameprep(iolist_to_binary(Server)),
IO = prepare_output(Output),
lists:foreach(
fun({Table, ConvertFun}) ->
case export(LServer, Table, IO, ConvertFun) of
{atomic, ok} -> ok;
+ {aborted, {no_exists, _}} ->
+ ?WARNING_MSG("Ignoring export for module ~s: "
+ "Mnesia table ~s doesn't exist (most likely "
+ "because the module is unused)",
+ [Module1, Table]);
{aborted, Reason} ->
?ERROR_MSG("Failed export for module ~p and table ~p: ~p",
[Module, Table, Reason])
end
- end, Module:export(Server)),
+ end, SQLMod:export(Server)),
close_output(Output, IO).
delete(Server) ->
diff --git a/src/misc.erl b/src/misc.erl
index 06d81cb88..80824f03e 100644
--- a/src/misc.erl
+++ b/src/misc.erl
@@ -33,7 +33,8 @@
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,
- compile_exprs/2, join_atoms/2, try_read_file/1, have_eimp/0]).
+ compile_exprs/2, join_atoms/2, try_read_file/1, have_eimp/0,
+ css_dir/0, img_dir/0, js_dir/0, read_css/1, read_img/1, read_js/1]).
%% Deprecated functions
-export([decode_base64/1, encode_base64/1]).
@@ -219,6 +220,51 @@ have_eimp() -> true.
have_eimp() -> false.
-endif.
+-spec css_dir() -> file:filename().
+css_dir() ->
+ case os:getenv("EJABBERD_CSS_PATH") of
+ false ->
+ case code:priv_dir(ejabberd) of
+ {error, _} -> filename:join(["priv", "css"]);
+ Path -> filename:join([Path, "css"])
+ end;
+ Path -> Path
+ end.
+
+-spec img_dir() -> file:filename().
+img_dir() ->
+ case os:getenv("EJABBERD_IMG_PATH") of
+ false ->
+ case code:priv_dir(ejabberd) of
+ {error, _} -> filename:join(["priv", "img"]);
+ Path -> filename:join([Path, "img"])
+ end;
+ Path -> Path
+ end.
+
+-spec js_dir() -> file:filename().
+js_dir() ->
+ case os:getenv("EJABBERD_JS_PATH") of
+ false ->
+ case code:priv_dir(ejabberd) of
+ {error, _} -> filename:join(["priv", "js"]);
+ Path -> filename:join([Path, "js"])
+ end;
+ Path -> Path
+ end.
+
+-spec read_css(file:filename()) -> {ok, binary()} | {error, file:posix()}.
+read_css(File) ->
+ read_file(filename:join(css_dir(), File)).
+
+-spec read_img(file:filename()) -> {ok, binary()} | {error, file:posix()}.
+read_img(File) ->
+ read_file(filename:join(img_dir(), File)).
+
+-spec read_js(file:filename()) -> {ok, binary()} | {error, file:posix()}.
+read_js(File) ->
+ read_file(filename:join(js_dir(), File)).
+
%%%===================================================================
%%% Internal functions
%%%===================================================================
@@ -230,3 +276,14 @@ set_node_id(PidStr, NodeBin) ->
[H|_] = string:tokens(ExtPidStr, "."),
[_|T] = string:tokens(PidStr, "."),
erlang:list_to_pid(string:join([H|T], ".")).
+
+-spec read_file(file:filename()) -> {ok, binary()} | {error, file:posix()}.
+read_file(Path) ->
+ case file:read_file(Path) of
+ {ok, Data} ->
+ {ok, Data};
+ {error, Why} = Err ->
+ ?ERROR_MSG("Failed to read file ~s: ~s",
+ [Path, file:format_error(Why)]),
+ Err
+ end.
diff --git a/src/mod_bosh.erl b/src/mod_bosh.erl
index ed12d569c..6ee580477 100644
--- a/src/mod_bosh.erl
+++ b/src/mod_bosh.erl
@@ -337,58 +337,16 @@ get_container_children(Heading) ->
].
get_style_cdata() ->
- <<"
- body {
- margin: 0;
- padding: 0;
- font-family: sans-serif;
- color: #fff;
- }
- h1 {
- font-size: 3em;
- color: #444;
- }
- p {
- line-height: 1.5em;
- color: #888;
- }
- a {
- color: #fff;
- }
- a:hover,
- a:active {
- text-decoration: underline;
- }
- .container {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: #424A55;
- background-image: -webkit-linear-gradient(270deg, rgba(48,52,62,0) 24%, #30353e 100%);
- background-image: linear-gradient(-180deg, rgba(48,52,62,0) 24%, #30353e 100%);
- }
- .section {
- padding: 3em;
- }
- .white.section {
- background: #fff;
- border-bottom: 4px solid #41AFCA;
- }
- .white.section a {
- text-decoration: none;
- color: #41AFCA;
- }
- .white.section a:hover,
- .white.section a:active {
- text-decoration: underline;
- }
- .block {
- margin: 0 auto;
- max-width: 900px;
- width: 100%;
- }">>.
+ case misc:read_css("bosh.css") of
+ {ok, Data} -> Data;
+ {error, _} -> <<>>
+ end.
get_image_src() ->
- <<"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAABACAYAAACgPErgAAAVtklEQVR42uydeXhU1d2ADzuyCcii4FZBZdEqVYtaUGqlikVwaRWsrWBRKIh8KgXcEAX9VECtoljcURAJICJWQLaKLAEjIHtBSNhC9oQkk2SW+36/52P+iIE5d5kzk4Fn3ud5H/7gIbnD3PPOveeeuVedygD9xLlQMh9yJ0D+GeokJAg1xcEWzOWYj0DBaQm0fXdaMCu8bROgpJVKksQj0+F84OXw/jRd7KVOdbbCFI6jdDNktlAnESVQNwSzOI6yhZBaV1UzAXiB4wj8CBtbqiRJXDIILtgL+zmOb8aoU5Xh0MMiEr5x6iSiAIZYROLo/aoayYMeocjbNlklSeKC9VAjBDM5IcEQfNZFnYq8AM8SkcJV6iSiCGYRkT1LVDWSA08Tkfy9MLaeOsUA2ot9xJHiW2KK+LW4Mvxnijg1/Pd9xQvFGiqJLUOhcT5kE5Etg0/VYI3TBOvbkyxYKURk2dpqDZb2gyEjC1SjUyBQdcQe4mtimujDHWXiD+Lr4g1iXZXkhISgBZBLZIaoU5GR0I2IVDxxkgVrNhH5z+pqDZb2g+FAJjQ4aYMFtBAfEzdils3iP8TkhYkqlMEZFuQQmcHqVGUBTOI40lLhz02TwUoGKxJAI/FxMYPYckB8Smyikvw/vlM/WHqAP4ifiHPF0fBsYyUkg5UM1okAbhV/JL5sE29TSZLBOhHJYCWDVRXgNPFVqpfXxQbJYCWDlQxWMlgRAc4VV5EYrBZ/kQxWMljJYFVLsBomdLCAS8RdJBZ7xMuSwTIcLKCO2EG8VrxdvEfsLV4F1vkJstq9BlidwN8hVleSLLgUuFzsEv6zs9g4EYIFNBAvFi87tm3WZWI7oHZ8glWnkWbb6ortCG/bsT+tDsBpcYrVpeIhzBHAHJni5XGOd5PwvnKJif0DaCh2/Pn7y/linbgFKx/qrjkWpffEHZo3qVhMBWuC2EVVA4ehay6sAYJgBeDocuh9iYqCL+F04DbxLTFNPMzxWGI6WCuBJ8Rfxi5YK34WLKC5BX3FVzj22n8S/fwcX/i9+1wcLraLVbAqr8OqgNOA31nwPLBc3HWCNU1+cbe4WBwjXhbD08A9eOcn8SNxmHij+EuxQ3iwdxP/Jr4pbsU7+2J5ehiCNuI9FkwN78sZol+0xDSYd6OH75R2BcZasALYKwZO0IWd4fd3LIS6g1XHeLBWQV1gsLgJ9wTBWgSBm1ScuBYuAvI4jrx9cOEZyiXzoUUIxhbDPm+fvFlL4PPe5oO14FslvAjnrYeXgYO4p0T8UrzJbLB2HlbCZMqbfwOjgrDD41HLCrE/LK1pKFYNxFS8sVDsK1Y5ctQeRf42HLcy3JMmNjIc6+ss+ETMRUuRH9Zd7SBS9QMwCFiDNzZBYAT4G5dTUcuC7KiClQ3dj8B6osYSAzOgoq2KMatgChF5e5RyQQXcVwL7MMNXsKOTuWAd2FBG9uhyyMMI6XPBd7GZYOUd8pP+UCn8hBEOr4KCbgYG7Ju4Z43YU0VB+LRoLu6ZpgyQAhflw2e4Y7lYS0XADzdZ8ANGsLaX88PgACHvwfLBY4Afo4T2Q9lNMZ68W0lE/J8oB5RDkxDMwDhF+XC4v6tgxZdcKLov+mBhYR4/FD8VRTRu93CE97RYTxkCuFfMxR13qSjIhoE+Tx9qPh9Mb6WqcBRqBuBli5hguQ4WUGsPvGsRKyw/hAaoGJEPy4lI4Uxlw3XHzu/XEVNKHk3QYAmWWDEecmraBqtaCE6HrCYeJpX34Jwc8eYYXp3cjHPSxWbKA9/Da3im+CeY1kBV4gNoUQILiB/6YD0JNYFP4jMoGFINwZqhNHwKLbfB5ji9/kdMBss8vk9MBcs8pasht7GLSIzHOQfFX6kYApyFuw/FFz38jveJitxBqhI3QMsS2IiQMMFaDFOJHyF45fZECdYOqA0sIX5YMPkOk8Eyz96xpoJlnvIZDgduazEfZ+SJVyoNhqO1DWcUim1dfJf2DTxTVgxFY1Ql+kOtivDYSJhgTYX7cIx1EJgmDhSvEbuK12XAiHyYCxThCKsQHmiXCMHa5+5T+BBY74qVX39vcUIhbLBwSkUO7DonBsEqBCtFfBjoHt6+34mjLfg34HO4feL8mw0HqwKsr8XRwA2E9x3xYY693jzHvWfZCAdheAZnWG6+1wecKd4oDhFHicPFvm6WI4SXRBTijOcdzi3+BefkifPAehLoI14Pqcdt/1x3YyNb/BisYUC38PvbU3wivOQhGHWw+sK5ASjAltBB8D8MQe05NXCBBZOBCmxJXwbTa1RnsDbCL4Pgx5ajeeJjYDVXEZgDNSugVwhW44j1sw0GKwjrX4El2kGzHDqVwzvO1x3d2dhMsPbMgjnaRZGroW0OjHcY1VIYeZHNMoZ0nPFP5YDwIJwp5miWinwt3u7w5z3g4lS1sc2c1Tl+Z0eTRXBkLKy1PWp7Ga4EAthSXAhHn4BQa5vX2zUIX1rRBKvU0fluYDaUt3F5xa5rNmzFlsfurs5grYP52JK7AVZcpBxSCrU3wvPOPtgzuxsI1gG3l9/90Cfg6Ihm/ONRBssn3udyceNVftiBLXNnaAZHbxcLNZs6uPXMFJdHCF84OeICluIIfQTzHI3jrI0Q7Kwc4myaxEqDtR2VC9bCCMDvOliZ0NGCCv1pwb7XlEdmQCtgOVp2bYU69aojWB/AVUEIoWcpfNlMeQD4q/0bs/GL6IK1JR3Wd/T4/lxZDJloKTgMrzXzFqyDJfBNT+WB2dD2kO1Eb9AP/zrhAAQ+xhl/d7A6fm0U977qbvPzr3YYwlkqArnQ2f4swb8SZjZXDtkG3SxsWSZ6HRt9RJ+rYP0H/omWdVNUlGyDxpm2b/i4P1VHsPbYnhrl7IQ/NlNRAPzDfqnH0o4eg1UEEy+N8mkl3e1P378e4CFYFnzRR0VBVzgP26BumqyqANQX92JPhm5FOXC2uJXoKBR7KA3AQuzZLzaMcJbwqv2pfXFL5YKV8BFaCvfCH1uqKDgKA0NOgzUAGuRp7664eaUyxD3Qpkz7u0oWxDtY/aBhFhzUD7hBN6goeRVq7IRVNkEY4y1YRx5VBiiHV9ByaK77YOW+pwxQCn9DS/4W2Fy7SgCuEEPYM1ETkXriSsxwRLxQe7RhjyV2VVW4E+ofhj1oSe2rXDAaGhTBAbQM72vo6VDzHQXrX9r7nxeXwqrLYWcNoFa0hj8F7rQiTuAV5cHIFvEM1jvQ3UJH+QJliN9Dj5B2APkWuw/Wrm3QrJ6hZyC20q/C9qfDsgbOg1WYDxe2NbRtdfTfZbXKIbW9x8ns30QISA1xEmbZLDbRzJEdwp6hqgqvwVWWdt9avVy5ZDL8Wj9VstbYwUwpdALKbIP1HxhJRAJlwPfhHWWzATeJafpz9bd/H89grYBRaHn+RmWQn2CdZtsyYWhjd8HaPVQZZKl2HV7AgvEXOw9W1pvKIDNhGFrevbVKACY6vPLWSHN0NUp8ThxnyJfECzRHWSlermaugfvRcuQu5ZLv4F60pN+lDLIZUmyDtRRmklAUPBzPYK2GT3Vf4IX+jZRBPoYniUioHA62dx6sQAVkt1MGSYXeFjoC3ZwFKyRm/14Z5Bu4OKidZwsOqjL4P8eeZSqBAMZgz0JVhQ3wOhEpLoLxZyqXHIAJRKS0CF48UxnkWehnG6wj8BUJxfK3VZQUuAiW/ntRwa+UYR6BP+gH+f6eLoK1CQprKoMchIv82itNGfc5DFY+FLUyvG0NS2E/Ecl8qcrg/w573kuwYP0Re1JVFd7RfvAWbYIU1/vJ2/AhETm6HlJqKIOshA4hKNMGK5RwwZrygYFHqC9zGqygNlgrP4vBjQW7hcCKHKy9tzh/kGrqd8ow+6CVX3tPovShKkw2PKuJx2G4tKEyyF6o4YPvicjhN1UlHD5TcGKCBetG7Nkq/ixA+doPNv9i5YFp2iuE1mJlmBxoEbR7kKqVcMGaOU1FyTpYq7tbg/NgbZ+tDLMLrrcJVi9ViTLtKfsq48E6CK392p0m/e8qTDE8RUQOHobmRoN1AGqWQZomWFMq33FE/C/2vJBgwboee/aKdVzMfXk6U3hLGyyMBysXWga1i5jTBqmchAvWY31VlGRqbyrmn64qka8NVsj4/MZg6IPgNFj7tV9iLfsRVtdSBsmATgEIaoJ/mwqzEwYTkWABbDhLGSQdGpdp12OVPF7lmQO7HU1gJxDAI9izs+oRVlAfrH8rDyzQXoAp3gxv1zJ8UaVTSDtHec9f1bqEmXQPiuUfwjdR/Se8AG0sOEpEUidVmax8n4iU58OKFoaPsJ5xE6x/wwT9pPuTRifdv4K79Nv3QZdK8yb90PLazcogH8HlIQgQkfl3ewjWpwn2yLH92JOmhFgHqwSGE5EyH7x/vjJICtyjX362s6f6Cf6hX9tSOB8Q+SJGfimmQPogZYCXoD9avh1WZYAORcvwfsogpbDeTbBmwW1oKR6mDJIGs4iIvxAeOLPSpGxn/dGYb6rhJRdPod2jn7nGQ7DWaQJSQzxLbC22iqFtxDvE7ThjRTyCNQ1uRkvqYGWQQzBH/yV3+R7zFOhhab8u8smt6iThN1AjB1brd+rnuqhKvAFX6hdz/rgBLqqpDJAHPS2w3ARrIpzth2IiEtgBM+spA2RBB/33unZ9qyrxDNTLg52a11MAS842dDrYRH/XhfxDMLyhh2AViK0jBKum+K5YKObE0CLc8WE8gvUStC7XbtuWrXBeHWWA7tDJp933dq+F8TXVHmgS1M4LFByE/q3VScB826OrwG7IqqsqcQvU3Q+70OJ7SEVJO6iVARsQHAcrTBosR8vrI5UBNsBctBQ+raowD6ag5ev3lQFmw3i0FM9SgiZYOiJ+KHMs4iUkFk+aC5aeLbAULYERygBB+BIt+8ZV3lHfQ8uRRaDqqASmF7Sxf8xV6SR1AjbBi+gphPIuUcb0aQQvwfoWBqDFKoEXuqooWAID0RLyw56OJ7j319WAhZZp/aOMVTegDC0ZfaII1sdKA9CPxOLWeAVrMgy0Hxu9ukS5NvEh+33v6Q6VT4uusCCElpnz4Ie6KgFZAWctgTS0lPjg2osiLDRtFwAfWoKHIfRrj/NCI0oAr8EqgAZ+2IOew7DkWuWBAPQvBT9ayuZo5uUWo8cH6z1FKwTXFkEOWio2Q1ndKIJVILa1XYGeGBSJbeIVrObQKBPS0VJ6ABZ7itY2eLAIO1JmqKqUwzzsWQyB9soFwGliQxUj/HCNs/tiH5hqM2hfxxarCPwPughBI80dEBwHK3wUOMDCjtKjbp5GlAn1vodxgIWeoO6hDGvhmqDtzygXA0/BN3VcPF/yAYfzO3crQRcsE+uxgIfEUqqXVUowGyw9I2EY9uRCxV0uls803AwTsacC3uisqjIEOgMV2GLlAeMgdK7NbW47Aq+Ke8UD4jsw4yyDj9ruIE6yoAxbyo/AjjOVhnehVQFk4ojQIgjeDNSNsGO3FP9mwVYEE8FqAbUOwwoccWAh+HuCVS/C9jUNwZ+BNByx9A0Hd4+ciiOOpELx3WBFumNB/RD0smARjti5FPrVNBCsQvECB9G6SvwaZ/xX/F/xf8S5YojoGRXvYM2C+j7nT8uZC9ZvI42NhdA8AAOALTgi5TkVic/gSRxjFQDzxKFiH7G7eJv4WAhWBqCc49i/Gya2US7hWPzuFf8iTrBgGVCOY4r/5HCupF8AV2wDazrwiDhEfEFcKGYRxkiwwlRAeyAb5+wQPxFHioPEseLn4gEcU7gdLm3q4PubTVw+ay9DTBGfCm/bKPFTcSfOyYe+FyrBQLAQPnfzyHdxSvg1F4hlYrGYHn5d94hNTnC/qxK8Uya2Mx8se0rhCsCHc7aCVXnfG2fB/Ao4hGPSV8O59VQkfgs1N8IiYkrBK8oFW+E5wIdndkx2eVXkeYxgPljh7bs1CCHigr8Ull7u4r26pBgKiBtb7lCCqWCFeVi5AKgtthE7iL8QGykNwGi8s0AJcQ9WmOnwIHEjlAFp5yg7noNmQCoxo2yNizmWoUG8Yon73oOzayiXAG8mZLDC/BkGlIBFTMkvg1v6eFhPc31mzKNVIf59hBJiECy/2FvFCOCcKObBesQjWNV/8aFiPxR0cbNRrcVVxITgauWAR6H+IdiNZxZPinJdyKSEDFaY5dAXyCY2HIJPeyqPvAlXANuJDT74/n4leA6WPUfFXjEKVjMxD/css/m5c7TBMsgmGBGI2VG+9T0UtvdyI7cme+EzjPPhROWACmiJp0/q8lzY/RdlgFUwDCjGGFZYXbDSb3Fx4aGzH77DKBmrIPdCFSUcWyU9B6Pk7ICMbkowFiz9fNFAZRjgGjGIOwLi1UrDBv0R1hJlmCLoFYIMs2Mj8D74m6poAIZbkGtmg/gIbmumHLID5uOK0GzIb294B7skCAssoiYLsh6FdWIk/D7Ycp7LZ7vV2Qejifo9CmYBo+Hb2oY/jQcC+4iKUAnwCmxvrgTjwdLzttjS0L7U3uOjwqYoG3ZqF19WvKVigA/aBGEaUEFUWFsh+CeTa50uCMAUC3LwRioEb1MueRw62a/DsXLE9yH4GxVD/HCjBfPEYlxxdDsUjgPrbCXMprTF/oiPnip9QnnEgnND8KwFu3DHdigfC4vaqhgBNA3CwxZsxB37xFdhUSelQRssM+wVh4iNPb7+RhwLyhHcs8PJM/+OHLv1ziaOw5cNmReoGAL8KgRvBWC/5e4IZh2EBomnqVgQgrYWDBUXApkRlhaUillgLRVfFm8AaiqPAN3ENWJ+2ExxJVjTIHQXBM9UcSQI51twLzBNXMSxQOSIeeEdchNYC469dq6HH+qd4DYpvwA+FPeIeeF/86Ch9+g0C34HTBC/FLeK2eHfkyWmiZ+LT4rXhddqxYUQ1LLgGmCMOF/8QcwKb1tOeFsXhNcu3QyWLhDmg2XPLvFZ8UpRO8iAVuL14nPiTrxRLnZVDjkC54TveJAfdjEcuUzFia/h9FLoDbwifhV+3XmVxsZ6cQ5Yj0LoChVPkI0TO4q9wLoDuFW8TrxAbGr4d9WwoAVwhni6SiCA+mIzsXn4/6S2i3/bIPzvasdw++pW2r6mYi2VIAC1wtvUPLyNdZUGz8EyTyj8s78KnzK+LL4kviPOE9eJeUTP/R5f+xlgnZEIY8OC5pXGRg2VJEkSTbBOXsaoJEmSJIN1EvC0SvJ/7dSxigEAHIDxf5FisAilbNbbWGSi5OZ7BgZPcS/hAZTNwFtYSVltit2iU/c9gxv8u75ffa/wSQ4ruR+ahySHldyZJiHJYSW3pnZIcliJHekrJDmsxMM60JQqIUkJh3WhJX1SKSTpzcN60J1udKQNfdOIaiFJLwxrTwua0Zj61PtjXfqgDtWpEJL04rCetKKBM5GUeVgnGoYkJR1Wka60o0ZIUuJhVWlLzZCk5MMqUyv0r/wCSDD/4sxS1q8AAAAASUVORK5CYII=">>.
+ case misc:read_img("bosh-logo.png") of
+ {ok, Img} ->
+ B64Img = base64:encode(Img),
+ <<"data:image/png;base64,", B64Img/binary>>;
+ {error, _} ->
+ <<>>
+ end.
diff --git a/src/mod_client_state.erl b/src/mod_client_state.erl
index efe6a260f..f7adb1c67 100644
--- a/src/mod_client_state.erl
+++ b/src/mod_client_state.erl
@@ -187,7 +187,7 @@ unregister_hooks(Host) ->
%%--------------------------------------------------------------------
-spec c2s_stream_started(c2s_state(), stream_start()) -> c2s_state().
c2s_stream_started(State, _) ->
- State#{csi_state => active, csi_queue => queue_new()}.
+ init_csi_state(State).
-spec c2s_authenticated_packet(c2s_state(), xmpp_element()) -> c2s_state().
c2s_authenticated_packet(C2SState, #csi{type = active}) ->
@@ -265,7 +265,10 @@ filter_other({Stanza, #{jid := JID} = C2SState} = Acc) when ?is_stanza(Stanza) -
Acc;
_ ->
?DEBUG("Won't add stanza for ~s to CSI queue", [jid:encode(JID)]),
- From = xmpp:get_from(Stanza),
+ From = case xmpp:get_from(Stanza) of
+ undefined -> JID;
+ F -> F
+ end,
C2SState1 = dequeue_sender(From, C2SState),
{Stanza, C2SState1}
end;
@@ -284,6 +287,10 @@ add_stream_feature(Features, Host) ->
%%--------------------------------------------------------------------
%% Internal functions.
%%--------------------------------------------------------------------
+-spec init_csi_state(c2s_state()) -> c2s_state().
+init_csi_state(C2SState) ->
+ C2SState#{csi_state => active, csi_queue => queue_new()}.
+
-spec enqueue_stanza(csi_type(), stanza(), c2s_state()) -> filter_acc().
enqueue_stanza(Type, Stanza, #{csi_state := inactive,
csi_queue := Q} = C2SState) ->
@@ -302,12 +309,18 @@ enqueue_stanza(_Type, Stanza, State) ->
-spec dequeue_sender(jid(), c2s_state()) -> c2s_state().
dequeue_sender(#jid{luser = U, lserver = S} = Sender,
- #{csi_queue := Q, jid := JID} = C2SState) ->
- ?DEBUG("Flushing packets of ~s@~s from CSI queue of ~s",
- [U, S, jid:encode(JID)]),
- {Elems, Q1} = queue_take(Sender, Q),
- C2SState1 = flush_stanzas(C2SState, Elems),
- C2SState1#{csi_queue => Q1}.
+ #{jid := JID} = C2SState) ->
+ case maps:get(csi_queue, C2SState, undefined) of
+ undefined ->
+ %% This may happen when the module is (re)loaded in runtime
+ init_csi_state(C2SState);
+ Q ->
+ ?DEBUG("Flushing packets of ~s@~s from CSI queue of ~s",
+ [U, S, jid:encode(JID)]),
+ {Elems, Q1} = queue_take(Sender, Q),
+ C2SState1 = flush_stanzas(C2SState, Elems),
+ C2SState1#{csi_queue => Q1}
+ end.
-spec flush_queue(c2s_state()) -> c2s_state().
flush_queue(#{csi_queue := Q, jid := JID} = C2SState) ->
diff --git a/src/mod_http_fileserver.erl b/src/mod_http_fileserver.erl
index c1c5b6461..f34936724 100644
--- a/src/mod_http_fileserver.erl
+++ b/src/mod_http_fileserver.erl
@@ -66,6 +66,8 @@
{-1, 403, [], <<"Forbidden">>}).
-define(HTTP_ERR_REQUEST_AUTH,
{-1, 401, ?REQUEST_AUTH_HEADERS, <<"Unauthorized">>}).
+-define(HTTP_ERR_HOST_UNKNOWN,
+ {-1, 410, [], <<"Host unknown">>}).
-define(DEFAULT_CONTENT_TYPE,
<<"application/octet-stream">>).
@@ -178,10 +180,15 @@ check_docroot_defined(DocRoot, Host) ->
end.
check_docroot_exists(DocRoot) ->
- case file:read_file_info(DocRoot) of
- {error, Reason} ->
- throw({error_access_docroot, DocRoot, Reason});
- {ok, FI} -> FI
+ case filelib:ensure_dir(filename:join(DocRoot, "foo")) of
+ ok ->
+ case file:read_file_info(DocRoot) of
+ {error, Reason} ->
+ throw({error_access_docroot, DocRoot, Reason});
+ {ok, FI} -> FI
+ end;
+ {error, Reason} ->
+ throw({error_access_docroot, DocRoot, Reason})
end.
check_docroot_is_dir(DRInfo, DocRoot) ->
@@ -297,18 +304,22 @@ code_change(_OldVsn, State, _Extra) ->
%% Returns the page to be sent back to the client and/or HTTP status code.
process(LocalPath, #request{host = Host, auth = Auth, headers = RHeaders} = Request) ->
?DEBUG("Requested ~p", [LocalPath]),
- try gen_server:call(get_proc_name(Host), {serve, LocalPath, Auth, RHeaders}) of
- {FileSize, Code, Headers, Contents} ->
- add_to_log(FileSize, Code, Request),
- {Code, Headers, Contents}
- catch
- exit:{noproc, _} ->
- ?ERROR_MSG("Received an HTTP request with Host ~p, but couldn't find the related "
- "ejabberd virtual host", [Request#request.host]),
- ejabberd_web:error(not_found)
+ try
+ VHost = ejabberd_router:host_of_route(Host),
+ {FileSize, Code, Headers, Contents} =
+ gen_server:call(get_proc_name(VHost),
+ {serve, LocalPath, Auth, RHeaders}),
+ add_to_log(FileSize, Code, Request#request{host = VHost}),
+ {Code, Headers, Contents}
+ catch _:{Why, _} when Why == noproc; Why == invalid_domain; Why == unregistered_route ->
+ ?DEBUG("Received an HTTP request with Host: ~s, "
+ "but couldn't find the related "
+ "ejabberd virtual host", [Host]),
+ {FileSize1, Code1, Headers1, Contents1} = ?HTTP_ERR_HOST_UNKNOWN,
+ add_to_log(FileSize1, Code1, Request#request{host = ?MYNAME}),
+ {Code1, Headers1, Contents1}
end.
-
serve(LocalPath, Auth, DocRoot, DirectoryIndices, CustomHeaders, DefaultContentType,
ContentTypes, UserAccess, IfModifiedSince) ->
CanProceed = case {UserAccess, Auth} of
@@ -425,7 +436,7 @@ add_to_log(File, FileSize, Code, Request) ->
IP = ip_to_string(element(1, Request#request.ip)),
Path = join(Request#request.path, "/"),
Query = case stringify_query(Request#request.q) of
- [] ->
+ <<"">> ->
"";
String ->
[$? | String]
@@ -445,11 +456,13 @@ add_to_log(File, FileSize, Code, Request) ->
FileSize, Referer, UserAgent]).
stringify_query(Q) ->
- join(
- lists:map(fun(E) ->
- lists:concat([binary_to_list(element(1, E)), "=", binary_to_list(element(2, E))])
- end, Q),
- "&").
+ stringify_query(Q, []).
+stringify_query([], Res) ->
+ join(lists:reverse(Res), "&");
+stringify_query([{nokey, _B} | Q], Res) ->
+ stringify_query(Q, Res);
+stringify_query([{A, B} | Q], Res) ->
+ stringify_query(Q, [join([A,B], "=") | Res]).
find_header(Header, Headers, Default) ->
case lists:keysearch(Header, 1, Headers) of
diff --git a/src/mod_mam_mnesia.erl b/src/mod_mam_mnesia.erl
index f498bc3c7..b672b4107 100644
--- a/src/mod_mam_mnesia.erl
+++ b/src/mod_mam_mnesia.erl
@@ -91,10 +91,10 @@ delete_old_user_messages(User, TimeStamp, Type) ->
ok
end
end,
+ NextRecord = mnesia:dirty_next(archive_msg, User),
case mnesia:transaction(F) of
{atomic, ok} ->
- delete_old_user_messages(mnesia:dirty_next(archive_msg, User),
- TimeStamp, Type);
+ delete_old_user_messages(NextRecord, TimeStamp, Type);
{aborted, Err} ->
?ERROR_MSG("Cannot delete old MAM messages: ~s", [Err]),
Err
diff --git a/src/mod_muc_log.erl b/src/mod_muc_log.erl
index cd4e195da..eba302337 100644
--- a/src/mod_muc_log.erl
+++ b/src/mod_muc_log.erl
@@ -502,186 +502,22 @@ make_dir_rec(Dir) ->
%% c("../../ejabberd/src/jlib.erl").
%% base64:encode(F1b).
-image_base64(<<"powered-by-erlang.png">>) ->
- <<"iVBORw0KGgoAAAANSUhEUgAAAGUAAAAfCAYAAAD+xQNoA"
- "AADN0lEQVRo3u1aP0waURz+rjGRRQ+nUyRCYmJyDPTapD"
- "ARaSIbTUjt1gVSh8ZW69aBAR0cWLSxCXWp59LR1jbdqKn"
- "GxoQuRZZrSYyHEVM6iZMbHewROA7u3fHvkr5vOn737vcu"
- "33ffu9/vcQz+gef5Cij6CkmSGABgFEH29r5SVvqIsTEOH"
- "o8HkiQxDBXEOjg9PcHc3BxuUSqsI8jR0REAUFGsCCoKFY"
- "WCBAN6AxyO0Z7cyMXFb6oGqSgAsIrJut9hMQlvdNbUhKW"
- "shLd3HtTF4jihShgVpRaBxKKmIGX5HL920/hz/BM2+zAm"
- "pn2YioQaxnECj0BiEYcrG0Tzzc8/rfudSm02jaVSm9Vr1"
- "MdG8rSKKXlJ7lHrfjouCut2IrC82BDPbe/gc+xlXez7Kx"
- "Ez63H4lmIN473Rh8Si1BKhRY6aEJI8pLmbjSPN0xOnBBI"
- "Lmg5RC6Lg28preKOzsNmHG8R1Bf0o7GdMucUslDy1pJLG"
- "2sndVVG0lq3c9vum4zmBR1kuwiYMN5ybmCYXxQg57ThFO"
- "TYznzpPO+IQi+IK+jXjg/YhuIJ+cIIHg+wQJoJ+2N3jYN"
- "3Olvk4ge/IU98spne+FfGtlslm16nna8fduntfDscoVjG"
- "JqUgIjz686ViFUdjP4N39x9Xq638viZVtlq2tLXKncLf5"
- "ticuZSWU5XOUshJKxxKtfdtdvs4OyNb/68urKvlluYizg"
- "wwu5SLK8jllu1t9ihYOlzdwdpBBKSvh+vKKzHkCj1JW3y"
- "1m+hSj13WjqOiJKK0qpXKhSFxJAYBvKYaZ9TjWRu4SiWi"
- "2LyDtb6wghGmn5HfTml16ILGA/G5al2DW7URYTFYrOU7g"
- "icQ020sYqYDM9CbdgqFd4vzHL03JfvLjk6ZgADAVCSEsJ"
- "vHsdL+utNYrm2ufZDVZSkzPKaQkW8kthpyS297BvRdRzR"
- "6DdTurJbPy9Ov1K6xr3HBPQuIMowR3asegUyDuU9SuUG+"
- "dmIGyZ0b7FBN9St3WunyC5yMsrVv7uXzRP58s/qKn6C4q"
- "lQoVxVIvd4YBwzBUFKs6ZaD27U9hEdcAN98Sx2IxykafI"
- "YrizbfESoB+dd9/KF/d/wX3cJvREzl1vAAAAABJRU5Erk"
- "Jggg==">>;
-image_base64(<<"valid-xhtml10.png">>) ->
- <<"iVBORw0KGgoAAAANSUhEUgAAAFgAAAAfCAMAAAEjEcpEA"
- "AACiFBMVEUAAADe5+fOezmtra3ejEKlhELvvWO9WlrehE"
- "LOe3vepaWclHvetVLGc3PerVKcCAj3vVqUjHOUe1JjlL0"
- "xOUpjjL2UAAC91ueMrc7vrVKlvdbW3u+EpcbO3ufO1ucY"
- "WpSMKQi9SiF7e3taWkoQEAiMczkQSoxaUkpzc3O1lEoIC"
- "ACEazEhGAgIAACEYzFra2utjELWcznGnEr/7+9jY2POaz"
- "HOYzGta2NShLVrlL05OUqctdacCADGa2ucAADGpVqUtc6"
- "1ORg5OTmlUikYGAiUezl7YzEYEAiUczkxMTG9nEqtIRDe"
- "3t4AMXu9lEoQCACMazEAKXspKSmljFrW1ta1jELOzs7n7"
- "/fGxsa9pVqEOSkpY5xznL29tZxahLXOpVr/99ZrY1L/79"
- "ZjUiljSikAOYTvxmMAMYScezmchFqUczGtlFp7c2utjFq"
- "UlJStxt73///39/9Ce61CSkq9xsZznMbW5+9Cc62MjIxC"
- "Qkrv9/fv7/fOzsbnlErWjIz/3mtCORhza1IpIRBzWjH/1"
- "mtCMRhzY1L/zmvnvVpSQiHOpVJrUinntVr3zmOEc1L3xm"
- "NaWlq1nFo5QkrGWim1lFoISpRSUlK1zt4hWpwASoz////"
- "///8xa6WUaykAQoxKe61KSkp7nMbWtWPe5+9jWlL39/f3"
- "9/fWrWNCQkLera3nvWPv7+85MRjntWPetVp7c1IxKRCUl"
- "HtKORh7a1IxIRCUjHtaSiHWrVIpIQhzWinvvVpaQiH/1m"
- "PWpVKMe1L/zmP/xmNrUiGErc4YGBj/73PG1ucQWpT/53O"
- "9nFoQUpS1SiEQEBC9zt69vb05c6UISoxSUko5a6UICAhS"
- "SkohUpS1tbXetWMAQoSUgD+kAAAA2HRSTlP/////////i"
- "P9sSf//dP////////////////////////////////////"
- "////////////8M////////////ef/////////////////"
- "/////////////////////////////////////////////"
- "//////////////////////9d/////////////////////"
- "///////////////AP//////////////CP//RP////////"
- "/////////////////////////////////////////////"
- "///////9xPp1gAAAFvUlEQVR42pVWi18URRwfy7vsYUba"
- "iqBRBFmICUQGVKcZckQeaRJQUCLeycMSfKGH0uo5NELpI"
- "vGQGzokvTTA85VHKTpbRoeJnPno/p1+M7t3txj20e/Nzu"
- "7Ofve7v/k9Zg4Vc+wRQMW0eyLx1ZSANeBDxVmxZZSwEUY"
- "kGAewm1eIBOMRvhv1UA+q8KXIVuxGdCelFYwxAnxOrxgb"
- "Y8Ti1t4VA0QHYz4x3FnVC8OVLXv9fkKGSWDoW/4lG6Vbd"
- "tBblesOs+MjmEmzJKNIJWFEfEQTCWNPFKvcKEymjLO1b8"
- "bwYQd1hCiiDCl5KsrDCIlhj4fSuvcpfSpgJmyv6dzeZv+"
- "nMPx3dhbt94II07/JZliEtm1N2RIYPkTYshwYm245a/zk"
- "WjJwcyFh6ZIcYxxmqiaDSYxhOhFUsqngi3Fzcj3ljdYDN"
- "E9uzA1YD/5MhnzW1KRqF7mYG8jFYXLcfLpjOe2LA0fuGq"
- "QrQHl10sdK0sFcFSOSlzF0BgXQH9h3QZDBI0ccNEhftjX"
- "uippBDD2/eMRiETmwwNEYHyqhdDyo22w+3QHuNbdve5a7"
- "eOkHmDVJ0ixNmfbz1h0qo/Q6GuSB2wQJQbpOjOQAl7woW"
- "SRJ0m2ewhvAOUiYYtZtaZL0CZZmtmVOQttLfr/dbveLZo"
- "drfrL7W75wG/JjqkQxoNTtNsTKELQpQL6/D5loaSmyTT8"
- "TUhsmi8iFA0hZiyltf7OiNKdarRm5w2So2lTNdPLuIzR+"
- "AiLj8VTRJaj0LmX4VhJ27f/VJV/yycilWPOrk8NkXi7Qq"
- "mj5bHqVZlJKZIRk1wFzKrt0WUbnXMPJ1fk4TJ5oWBA61p"
- "1V76DeIs0MX+s3GxRlA1vtw83KhgNphc1nyErLO5zcvbO"
- "srq+scbZnpzc6QVFPenLwGxmC+BOfYI+DN55QYddh4Q/N"
- "E/yGYYj4TOGNngQavAZnzzTovEA+kcMJ+247uYexNA+4F"
- "svjmuv662jsWxPZx2xg890bYMYnTgya7bjmCiEY0qgJ0v"
- "MF3c+NoFdPyzxz6V3Uxs3AOWCDchRvOsQtBrbFsrT2fhH"
- "Ec7ByGzu/dA4IO0A3HdfeP9yMqAwP6NPEb6cbwn0PWVU1"
- "7/FDBQh/CPIrbfcg027IZrsAT/Bf3FNWyn9RSR4cvvwn3"
- "e4HFmYPDl/thYcRVi8qPEoXVUWBl6FTBFTtnqmKKg5wnl"
- "F4wZ1yeLv7TiwXKektE+iDBNicWEyLpnFhfDkpJc3q2kh"
- "SPyQBbE0dMJnOoDzTwGsI7cdyMkL5gWqUjCF6Txst/twx"
- "Cv1WzzHoy21ZDQ1xnuDzdPDWR4knr14v0tYn3IxaMFFdi"
- "MOlEOJHw1jOQ4sWt5rQopRkXZhMEi7pmeDCVWBlfUKwhM"
- "Z7rsF6elKsvbwiKxgxIdewa3ErsaYomCVZFYJb0GUu3Jq"
- "GUNoplBxYiYby8vLBFWef+Cri4/I1sbQ/1OtYTrNtdXS+"
- "rSe7kQ52eSObL99/iErCWUjCy5W4JLygmCouGfG9x9fmx"
- "17XhBuDCaOerbt538erta7TFktLvdHghZcCbcPQO33zIJ"
- "G9kxF5hoVXnzTzRz0r5js8oTj6uyPkGRf346HOLcasgFe"
- "xueNUWFPtuFKzjoSFYYedhwVlhsRVYWWJpltv1XPQT1Rl"
- "0bjZIBlb1XujVDzY/Kj4k6Ku3+Z0jo1owjVzDpFTXe1ju"
- "vBSWNFmNWGZy8LvzUl5PN4JCwyNDzbQ0aAj4Zrjz0FatG"
- "JJYhvq4j7mGSpvytGFlZtHf2C4o/28Zu8z7wo7eYPfXys"
- "nF0i9NnPh1t1zR7VBb9GqaOXhtTmHQdgMFXE+Z608cnpO"
- "DdZdjL+TuDY44Q38kJXHhccWLoOd9uv1AwwvO+48uu+fa"
- "CSJPJ1bmy6ThyvpivBmYWgjxPDPAp7JTemY/yGKFEiRt/"
- "jG/2P79s8KCwoLCgoLC/khUBA5F0SfQZ+RYfpNE/4Xosm"
- "q7jsZAJsAAAAASUVORK5CYII=">>;
-image_base64(<<"vcss.png">>) ->
- <<"iVBORw0KGgoAAAANSUhEUgAAAFgAAAAfCAMAAABUFvrSA"
- "AABKVBMVEUAAAAjIx8MR51ZVUqAdlmdnZ3ejEWLDAuNjY"
- "1kiMG0n2d9fX19Ghfrp1FtbW3y39+3Ph6lIRNdXV2qJBF"
- "cVUhcVUhPT0/dsmpUfLr57+/u7u4/PDWZAACZAADOp1Gd"
- "GxG+SyTgvnNdSySzk16+mkuxw+BOS0BOS0DOzs7MzMy4T"
- "09RRDwsJBG+vr73wV6fkG6eCQRFcLSurq6/X1+ht9nXfz"
- "5sepHuwV59ZTHetFjQ2+wMCQQ2ZK5tWCsmWajsz8+Sq9N"
- "MPh4hVaY8MRj///////////////////////9MTEyOp9Lu"
- "8vhXU1A8PDyjOSTBz+YLRJ2rLy8sLCwXTaKujEUcHByDn"
- "82dfz7/zGafDw+fDw+zRSlzlMcMDAyNcji1tbXf5vIcFg"
- "vATJOjAAAAY3RSTlP/8/////////////////8A//////P"
- "/////ov//8//////////////z///T//////////+i////"
- "//////////8w/////6IA/xAgMP//////////8////////"
- "/8w0/////////+zehebAAACkUlEQVR42u2VfVPTQBDG19"
- "VqC6LY+lKrRIxFQaFSBPuSvhBPF8SIUZK2J5Yav/+HcO8"
- "uZdLqTCsU/nKnyWwvk1/unnt2D9ZmH+8/cMAaTRFy+ng6"
- "9/yiwC/+gy8R3McGv5zHvGJEGAdR4eBgi1IbZwevIEZE2"
- "4pFtBtzG1Q4AoD5zvw5pEDcJvIQV/TE3/l+H9GnNJwcdA"
- "BS5wAbFQLMqI98/UReoAaOTlaJsp0zaHx7LwZvY0BUR2x"
- "pWTzqam0gzY8KGzG4MhBCNGucha4QbpETy+Yk/BP85nt7"
- "34AjpQLTsE4ZFpf/dnkUCglXVNYB+OfUZJHvAqAoa45Oe"
- "uPgm4+Xjtv7xm4N7PMV4C61+Mrz3H2WImm3ATiWrAiwZR"
- "WcUA5Ej4dgIEMxDv6yxHHcNuAutnjv2HZ1NeuycoVPh0m"
- "wC834zZC9Ao5dkZZKwLVGwT+WdLw0YOZ1saEkUDoT+QGW"
- "KZ0E2xpcrPakVW2KXwyUtYEtlEAj3GXD/fYwrryAdeiyG"
- "qidQSw1eqtJcA8cZq4zXqhPuCBYE1fKJjh/5X6MwRm9c2"
- "xf7WVdLf5oSdt64esVIwVAKC1HJ2oli8vj3L0YzC4zjkM"
- "agt+arDAs6bApbL1RVlWIqrJbreqKZmh4y6VR7rAJeUYD"
- "VRj9VqRXkErpJ9lbEwtE83KlIfeG4p52t7zWIMO1XcaGz"
- "54uUyet+hBM7BXXDS8Xc5+8Gmmbu1xwSoGIokA3oTptQe"
- "cQ4Iimm/Ew7jwbPfMi3TM91T9XVIGo+W9xC8oWpugVCXL"
- "uwXijjxJ3r/6PjX7nlFua8QmyM+TO/Gja2TTc2Z95C5ua"
- "ewGH6cJi6bJO6Z+TY276eH3tbgy+/3ly3Js+rj66osG/A"
- "V5htgaQ9SeRAAAAAElFTkSuQmCC">>;
-image_base64(<<"powered-by-ejabberd.png">>) ->
- <<"iVBORw0KGgoAAAANSUhEUgAAAGUAAAAfCAMAAADJG/NaA"
- "AAAw1BMVEUAAAAjBgYtBAM5AwFCAAAYGAJNAABcAABIDQ"
- "5qAAAoJRV7AACFAAAoKSdJHByLAAAwLwk1NQA1MzFJKyo"
- "4NxtDQQBEQT5KSCxSTgBSUBlgQ0JYSEpZWQJPUU5hYABb"
- "W0ZiYClcW1poaCVwbQRpaDhzYWNsakhuZ2VrbFZ8dwCEg"
- "AB3dnd4d2+OjACDhYKcmACJi4iQkpWspgCYmJm5swCmqa"
- "zEwACwsbS4ub3X0QLExsPLyszW1Nnc3ODm5ugMBwAWAwP"
- "Hm1IFAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJ"
- "cEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfVCRQOBA7VB"
- "kCMAAACcElEQVRIx72WjXKiMBSFQalIFbNiy1pdrJZaRV"
- "YR5deGwPs/VRNBSBB2OjvQO0oYjPfj5J6bCcdx8i2Uldx"
- "KcDhk1HbIPwFBF/kHKJfjPSVAyIRHF9rRZ4sUX3EDdWOv"
- "1+u2tESaavpnYTbv9zvd0WwDy3/QcGQXlH5uTxB1l07MJ"
- "lRpsUei0JF6Qi+OHyGK7ijXxPklHe/umIllim3iUBMJDI"
- "EULxxPP0TVWhhKJoN9fUpdmQLteV8aDgEAg9gIcTjL4F4"
- "L+r4WVKEF+rbJdwYYAoQHY+oQjnGootyKwxapoi73WkyF"
- "FySQBv988naEEp4+YMMec5VUCQDJTscEy7Kc0HsLmqNE7"
- "rovDjMpIHHGYeidXn4TQcaxMYqP3RV3C8oCl2WvrlSPaN"
- "pGZadRnmPGCk8ylM2okAJ4i9TEe1KersXxSl6jUt5uayi"
- "IodirtcKLOaWblj50wiyMv1F9lm9TUDArGAD0FmEpvCUs"
- "VoZy6dW81Fg0aDaHogQa36ekAPG5DDGsbdZrGsrzZUnzv"
- "Bo1I2tLmuL69kSitAweyHKN9b3leDfQMnu3nIIKWfmXnq"
- "GVKedJT6QpICbJvf2f8aOsvn68v+k7/cwUQdPoxaMoRTn"
- "KFHNlKsKQphCTOa84u64vpi8bH31CqsbF6lSONRTkTyQG"
- "Arq49/fEvjBwz4eDS2/JpaXRNOoXRD/VmOrDVTJJRIZCT"
- "Lav3VrqbPvP3vdduGEhQJzilncbpSA4F3vsihErO+dayv"
- "/sY5/yRE0GDEXCu2VoNiMlo5i+P2KlgMEvTNk2eYa5XEy"
- "h12Ex17Z8vzQUR3KEPbYd6XG87eC4Ly75RneS5ZYHAAAA"
- "AElFTkSuQmCC">>.
-
create_image_files(Images_dir) ->
+ ?INFO_MSG("here = ~p", [Images_dir]),
Filenames = [<<"powered-by-ejabberd.png">>,
<<"powered-by-erlang.png">>, <<"valid-xhtml10.png">>,
<<"vcss.png">>],
- lists:foreach(fun (Filename) ->
- Filename_full = fjoin([Images_dir, Filename]),
- {ok, F} = file:open(Filename_full, [write]),
- Image = base64:decode(image_base64(Filename)),
- io:format(F, <<"~s">>, [Image]),
- file:close(F)
- end,
- Filenames),
- ok.
+ lists:foreach(
+ fun(Filename) ->
+ Src = filename:join([misc:img_dir(), Filename]),
+ Dst = fjoin([Images_dir, Filename]),
+ case file:copy(Src, Dst) of
+ {ok, _} -> ok;
+ {error, Why} ->
+ ?ERROR_MSG("Failed to copy ~s to ~s",
+ [Src, Dst, file:format_error(Why)])
+ end
+ end, Filenames).
fw(F, S) -> fw(F, S, [], html).
@@ -768,77 +604,10 @@ put_header(F, Room, Date, CSSFile, Lang, Hour_offset,
put_header_css(F, false) ->
fw(F, <<"<style type=\"text/css\">">>),
fw(F, <<"<!--">>),
- fw(F,
- <<".ts {color: #AAAAAA; text-decoration: "
- "none;}">>),
- fw(F,
- <<".mrcm {color: #009900; font-style: italic; "
- "font-weight: bold;}">>),
- fw(F,
- <<".msc {color: #009900; font-style: italic; "
- "font-weight: bold;}">>),
- fw(F,
- <<".msm {color: #000099; font-style: italic; "
- "font-weight: bold;}">>),
- fw(F, <<".mj {color: #009900; font-style: italic;}">>),
- fw(F, <<".ml {color: #009900; font-style: italic;}">>),
- fw(F, <<".mk {color: #009900; font-style: italic;}">>),
- fw(F, <<".mb {color: #009900; font-style: italic;}">>),
- fw(F, <<".mnc {color: #009900; font-style: italic;}">>),
- fw(F, <<".mn {color: #0000AA;}">>),
- fw(F, <<".mne {color: #AA0099;}">>),
- fw(F,
- <<"a.nav {color: #AAAAAA; font-family: "
- "monospace; letter-spacing: 3px; text-decorati"
- "on: none;}">>),
- fw(F,
- <<"div.roomtitle {border-bottom: #224466 "
- "solid 3pt; margin-left: 20pt;}">>),
- fw(F,
- <<"div.roomtitle {color: #336699; font-size: "
- "24px; font-weight: bold; font-family: "
- "sans-serif; letter-spacing: 3px; text-decorat"
- "ion: none;}">>),
- fw(F,
- <<"a.roomjid {color: #336699; font-size: "
- "24px; font-weight: bold; font-family: "
- "sans-serif; letter-spacing: 3px; margin-left: "
- "20pt; text-decoration: none;}">>),
- fw(F,
- <<"div.logdate {color: #663399; font-size: "
- "20px; font-weight: bold; font-family: "
- "sans-serif; letter-spacing: 2px; border-botto"
- "m: #224466 solid 1pt; margin-left:80pt; "
- "margin-top:20px;}">>),
- fw(F,
- <<"div.roomsubject {color: #336699; font-size: "
- "18px; font-family: sans-serif; margin-left: "
- "80pt; margin-bottom: 10px;}">>),
- fw(F,
- <<"div.rc {color: #336699; font-size: 12px; "
- "font-family: sans-serif; margin-left: "
- "50%; text-align: right; background: "
- "#f3f6f9; border-bottom: 1px solid #336699; "
- "border-right: 4px solid #336699;}">>),
- fw(F,
- <<"div.rct {font-weight: bold; background: "
- "#e3e6e9; padding-right: 10px;}">>),
- fw(F, <<"div.rcos {padding-right: 10px;}">>),
- fw(F, <<"div.rcoe {color: green;}">>),
- fw(F, <<"div.rcod {color: red;}">>),
- fw(F, <<"div.rcoe:after {content: \": v\";}">>),
- fw(F, <<"div.rcod:after {content: \": x\";}">>),
- fw(F, <<"div.rcot:after {}">>),
- fw(F,
- <<".legend {width: 100%; margin-top: 30px; "
- "border-top: #224466 solid 1pt; padding: "
- "10px 0px 10px 0px; text-align: left; "
- "font-family: monospace; letter-spacing: "
- "2px;}">>),
- fw(F,
- <<".w3c {position: absolute; right: 10px; "
- "width: 60%; text-align: right; font-family: "
- "monospace; letter-spacing: 1px;}">>),
+ case misc:read_css("muc.css") of
+ {ok, Data} -> fw(F, Data);
+ {error, _} -> ok
+ end,
fw(F, <<"//-->">>),
fw(F, <<"</style>">>);
put_header_css(F, CSSFile) ->
@@ -849,16 +618,10 @@ put_header_css(F, CSSFile) ->
put_header_script(F) ->
fw(F, <<"<script type=\"text/javascript\">">>),
- fw(F, <<"function sh(e) // Show/Hide an element">>),
- fw(F,
- <<"{if(document.getElementById(e).style.display="
- "='none')">>),
- fw(F,
- <<"{document.getElementById(e).style.display='bl"
- "ock';}">>),
- fw(F,
- <<"else {document.getElementById(e).style.displa"
- "y='none';}}">>),
+ case misc:read_js("muc.js") of
+ {ok, Data} -> fw(F, Data);
+ {error, _} -> ok
+ end,
fw(F, <<"</script>">>).
put_room_config(_F, _RoomConfig, _Lang, plaintext) ->
diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl
index 8521238e8..6e83a374f 100644
--- a/src/mod_muc_room.erl
+++ b/src/mod_muc_room.erl
@@ -1014,7 +1014,13 @@ do_process_presence(Nick, #presence{from = From, type = available, lang = Lang}
From, Packet, StateData),
NewState = add_user_presence(From, Stanza,
StateData),
- send_new_presence(From, NewState, StateData),
+ case xmpp:has_subtag(Packet, #muc{}) of
+ true ->
+ send_initial_presences_and_messages(
+ From, Nick, Packet, NewState, StateData);
+ false ->
+ send_new_presence(From, NewState, StateData)
+ end,
NewState
end
end;
@@ -1235,7 +1241,7 @@ get_error_condition(undefined) ->
-spec get_error_text(stanza_error()) -> binary().
get_error_text(#stanza_error{text = Txt}) ->
- xmpp:get_text([Txt]).
+ xmpp:get_text(Txt).
-spec make_reason(stanza(), jid(), state(), binary()) -> binary().
make_reason(Packet, From, StateData, Reason1) ->
@@ -1254,8 +1260,16 @@ expulse_participant(Packet, From, StateData, Reason1) ->
LJID = jid:tolower(From),
{ok, #user{nick = Nick}} = (?DICT):find(LJID, StateData#state.users),
case (?DICT):find(Nick, StateData#state.nicks) of
- {ok, [_, _ | _]} -> ok;
- _ -> send_new_presence(From, NewState, StateData)
+ {ok, [_, _ | _]} ->
+ Aff = get_affiliation(From, StateData),
+ Item = #muc_item{affiliation = Aff, role = none, jid = From},
+ Pres = xmpp:set_subtag(
+ Packet, #muc_user{items = [Item],
+ status_codes = [110]}),
+ send_wrapped(jid:replace_resource(StateData#state.jid, Nick),
+ From, Pres, ?NS_MUCSUB_NODES_PRESENCE, StateData);
+ _ ->
+ send_new_presence(From, NewState, StateData)
end,
remove_online_user(From, NewState).
@@ -1869,11 +1883,8 @@ add_new_user(From, Nick, Packet, StateData) ->
From, Packet,
add_online_user(From, Nick, Role,
StateData)),
- send_existing_presences(From, NewState),
- send_initial_presence(From, NewState, StateData),
- History = get_history(Nick, Packet, NewState),
- send_history(From, History, NewState),
- send_subject(From, StateData),
+ send_initial_presences_and_messages(
+ From, Nick, Packet, NewState, StateData),
NewState;
true ->
set_subscriber(From, Nick, Nodes, StateData)
@@ -2068,6 +2079,15 @@ presence_broadcast_allowed(JID, StateData) ->
Role = get_role(JID, StateData),
lists:member(Role, (StateData#state.config)#config.presence_broadcast).
+-spec send_initial_presences_and_messages(
+ jid(), binary(), presence(), state(), state()) -> ok.
+send_initial_presences_and_messages(From, Nick, Presence, NewState, OldState) ->
+ 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_initial_presence(jid(), state(), state()) -> ok.
send_initial_presence(NJID, StateData, OldStateData) ->
send_new_presence1(NJID, <<"">>, true, StateData, OldStateData).
diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl
index 0f344cc18..e84d727eb 100644
--- a/src/mod_pubsub.erl
+++ b/src/mod_pubsub.erl
@@ -89,7 +89,7 @@
%% API and gen_server callbacks
-export([start/2, stop/1, init/1,
handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3, depends/2, export/1, mod_opt_type/1]).
+ terminate/2, code_change/3, depends/2, mod_opt_type/1]).
%%====================================================================
%% API
@@ -447,10 +447,7 @@ disco_identity(Host, Node, From) ->
{result, _} ->
{result, [#identity{category = <<"pubsub">>, type = <<"pep">>},
#identity{category = <<"pubsub">>, type = <<"leaf">>,
- name = case get_option(Options, title) of
- false -> <<>>;
- Title -> Title
- end}]};
+ name = get_option(Options, title, <<>>)}]};
_ ->
{result, []}
end
@@ -514,10 +511,7 @@ disco_items(Host, <<>>, From) ->
{result, _} ->
[#disco_item{node = Node,
jid = jid:make(Host),
- name = case get_option(Options, title) of
- false -> <<>>;
- Title -> Title
- end} | Acc];
+ name = get_option(Options, title, <<>>)} | Acc];
_ ->
Acc
end
@@ -829,7 +823,8 @@ process_disco_info(#iq{from = From, to = To, lang = Lang, type = get,
[ServerHost, ?MODULE, <<>>, <<>>]),
case iq_disco_info(Host, Node, From, Lang) of
{result, IQRes} ->
- xmpp:make_iq_result(IQ, IQRes#disco_info{node = Node, xdata = Info});
+ XData = IQRes#disco_info.xdata ++ Info,
+ xmpp:make_iq_result(IQ, IQRes#disco_info{node = Node, xdata = XData});
{error, Error} ->
xmpp:make_error(IQ, Error)
end.
@@ -938,14 +933,25 @@ node_disco_info(Host, Node, From) ->
{result, disco_info()} | {error, stanza_error()}.
node_disco_info(Host, Node, _From, _Identity, _Features) ->
Action =
- fun(#pubsub_node{type = Type, options = Options}) ->
+ fun(#pubsub_node{id = Nidx, type = Type, options = Options}) ->
NodeType = case get_option(Options, node_type) of
collection -> <<"collection">>;
_ -> <<"leaf">>
end,
+ Affs = case node_call(Host, Type, get_node_affiliations, [Nidx]) of
+ {result, Result} -> Result;
+ _ -> []
+ end,
+ Meta = [{title, get_option(Options, title, <<>>)},
+ {description, get_option(Options, description, <<>>)},
+ {owner, [jid:make(LJID) || {LJID, Aff} <- Affs, Aff =:= owner]},
+ {publisher, [jid:make(LJID) || {LJID, Aff} <- Affs, Aff =:= publisher]},
+ {num_subscribers, length([LJID || {LJID, Aff} <- Affs, Aff =:= subscriber])}],
+ XData = #xdata{type = result,
+ fields = pubsub_meta_data:encode(Meta)},
Is = [#identity{category = <<"pubsub">>, type = NodeType}],
Fs = [?NS_PUBSUB | [feature(F) || F <- plugin_features(Host, Type)]],
- {result, #disco_info{identities = Is, features = Fs}}
+ {result, #disco_info{identities = Is, features = Fs, xdata = [XData]}}
end,
case transaction(Host, Node, Action, sync_dirty) of
{result, {_, Result}} -> {result, Result};
@@ -1562,6 +1568,7 @@ delete_node(Host, Node, Owner) ->
RNidx = RNode#pubsub_node.id,
RType = RNode#pubsub_node.type,
ROptions = RNode#pubsub_node.options,
+ unset_cached_item(RH, RNidx),
broadcast_removed_node(RH, RN, RNidx, RType, ROptions, SubsByDepth),
ejabberd_hooks:run(pubsub_delete_node,
ServerHost,
@@ -1576,6 +1583,7 @@ delete_node(Host, Node, Owner) ->
lists:foreach(fun ({RNode, _RSubs}) ->
{RH, RN} = RNode#pubsub_node.nodeid,
RNidx = RNode#pubsub_node.id,
+ unset_cached_item(RH, RNidx),
ejabberd_hooks:run(pubsub_delete_node,
ServerHost,
[ServerHost, RH, RN, RNidx])
@@ -1587,6 +1595,7 @@ delete_node(Host, Node, Owner) ->
end;
{result, {TNode, {_, Result}}} ->
Nidx = TNode#pubsub_node.id,
+ unset_cached_item(Host, Nidx),
ejabberd_hooks:run(pubsub_delete_node, ServerHost,
[ServerHost, Host, Node, Nidx]),
case Result of
@@ -3789,7 +3798,7 @@ purge_offline(Host, LJID, Node) ->
Nidx = Node#pubsub_node.id,
Type = Node#pubsub_node.type,
Options = Node#pubsub_node.options,
- case node_action(Host, Type, get_items, [Nidx, service_jid(Host), none]) of
+ case node_action(Host, Type, get_items, [Nidx, service_jid(Host), undefined]) of
{result, {[], _}} ->
ok;
{result, {Items, _}} ->
@@ -3819,9 +3828,6 @@ purge_offline(Host, LJID, Node) ->
Error
end.
-export(Server) ->
- pubsub_db_sql:export(Server).
-
mod_opt_type(access_createnode) -> fun acl:access_rules_validator/1;
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(host) -> fun iolist_to_binary/1;
diff --git a/src/mod_push.erl b/src/mod_push.erl
index 2ca0bf525..2fa62cacd 100644
--- a/src/mod_push.erl
+++ b/src/mod_push.erl
@@ -56,26 +56,27 @@
-type c2s_state() :: ejabberd_c2s:state().
-type timestamp() :: erlang:timestamp().
-type push_session() :: {timestamp(), ljid(), binary(), xdata()}.
+-type err_reason() :: notfound | db_failure.
-callback init(binary(), gen_mod:opts())
-> any().
-callback store_session(binary(), binary(), timestamp(), jid(), binary(),
xdata())
- -> {ok, push_session()} | error.
+ -> {ok, push_session()} | {error, err_reason()}.
-callback lookup_session(binary(), binary(), jid(), binary())
- -> {ok, push_session()} | error.
+ -> {ok, push_session()} | {error, err_reason()}.
-callback lookup_session(binary(), binary(), timestamp())
- -> {ok, push_session()} | error.
+ -> {ok, push_session()} | {error, err_reason()}.
-callback lookup_sessions(binary(), binary(), jid())
- -> {ok, [push_session()]} | error.
+ -> {ok, [push_session()]} | {error, err_reason()}.
-callback lookup_sessions(binary(), binary())
- -> {ok, [push_session()]} | error.
+ -> {ok, [push_session()]} | {error, err_reason()}.
-callback lookup_sessions(binary())
- -> {ok, [push_session()]} | error.
+ -> {ok, [push_session()]} | {error, err_reason()}.
-callback delete_session(binary(), binary(), timestamp())
- -> ok | error.
+ -> ok | {error, err_reason()}.
-callback delete_old_sessions(binary() | global, erlang:timestamp())
- -> any().
+ -> ok | {error, err_reason()}.
-callback use_cache(binary())
-> boolean().
-callback cache_nodes(binary())
@@ -253,29 +254,39 @@ process_iq(#iq{lang = Lang, sub_els = [#push_enable{node = <<>>}]} = IQ) ->
xmpp:make_error(IQ, xmpp:err_feature_not_implemented(Txt, Lang));
process_iq(#iq{from = #jid{lserver = LServer} = JID,
to = #jid{lserver = LServer},
+ lang = Lang,
sub_els = [#push_enable{jid = PushJID,
node = Node,
xdata = XData}]} = IQ) ->
case enable(JID, PushJID, Node, XData) of
ok ->
xmpp:make_iq_result(IQ);
- error ->
- xmpp:make_error(IQ, xmpp:err_internal_server_error())
+ {error, db_failure} ->
+ Txt = <<"Database failure">>,
+ xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang));
+ {error, notfound} ->
+ Txt = <<"User session not found">>,
+ xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang))
end;
process_iq(#iq{from = #jid{lserver = LServer} = JID,
to = #jid{lserver = LServer},
+ lang = Lang,
sub_els = [#push_disable{jid = PushJID,
node = Node}]} = IQ) ->
case disable(JID, PushJID, Node) of
ok ->
xmpp:make_iq_result(IQ);
- error ->
- xmpp:make_error(IQ, xmpp:err_item_not_found())
+ {error, db_failure} ->
+ Txt = <<"Database failure">>,
+ xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang));
+ {error, notfound} ->
+ Txt = <<"Push record not found">>,
+ xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang))
end;
process_iq(IQ) ->
xmpp:make_error(IQ, xmpp:err_not_allowed()).
--spec enable(jid(), jid(), binary(), xdata()) -> ok | error.
+-spec enable(jid(), jid(), binary(), xdata()) -> ok | {error, err_reason()}.
enable(#jid{luser = LUser, lserver = LServer, lresource = LResource} = JID,
PushJID, Node, XData) ->
case ejabberd_sm:get_session_sid(LUser, LServer, LResource) of
@@ -285,18 +296,18 @@ enable(#jid{luser = LUser, lserver = LServer, lresource = LResource} = JID,
?INFO_MSG("Enabling push notifications for ~s",
[jid:encode(JID)]),
ejabberd_c2s:cast(PID, push_enable);
- error ->
+ {error, _} = Err ->
?ERROR_MSG("Cannot enable push for ~s: database error",
[jid:encode(JID)]),
- error
+ Err
end;
none ->
?WARNING_MSG("Cannot enable push for ~s: session not found",
[jid:encode(JID)]),
- error
+ {error, notfound}
end.
--spec disable(jid(), jid(), binary() | undefined) -> ok | error.
+-spec disable(jid(), jid(), binary() | undefined) -> ok | {error, err_reason()}.
disable(#jid{luser = LUser, lserver = LServer, lresource = LResource} = JID,
PushJID, Node) ->
case ejabberd_sm:get_session_sid(LUser, LServer, LResource) of
@@ -308,7 +319,7 @@ disable(#jid{luser = LUser, lserver = LServer, lresource = LResource} = JID,
?WARNING_MSG("Session not found while disabling push for ~s",
[jid:encode(JID)])
end,
- if Node /= undefined ->
+ if Node /= <<>> ->
delete_session(LUser, LServer, PushJID, Node);
true ->
delete_sessions(LUser, LServer, PushJID)
@@ -388,7 +399,7 @@ c2s_handle_cast(State, push_disable) ->
c2s_handle_cast(State, _Msg) ->
State.
--spec remove_user(binary(), binary()) -> ok | error.
+-spec remove_user(binary(), binary()) -> ok | {error, err_reason()}.
remove_user(LUser, LServer) ->
?INFO_MSG("Removing any push sessions of ~s@~s", [LUser, LServer]),
Mod = gen_mod:db_mod(LServer, ?MODULE),
@@ -403,7 +414,7 @@ notify(#{jid := #jid{luser = LUser, lserver = LServer}, sid := {TS, _}}) ->
case lookup_session(LUser, LServer, TS) of
{ok, Client} ->
notify(LUser, LServer, [Client]);
- error ->
+ _Err ->
ok
end.
@@ -440,7 +451,7 @@ notify(LServer, PushLJID, Node, XData, HandleResponse) ->
%% Internal functions.
%%--------------------------------------------------------------------
-spec store_session(binary(), binary(), timestamp(), jid(), binary(), xdata())
- -> {ok, push_session()} | error.
+ -> {ok, push_session()} | {error, err_reason()}.
store_session(LUser, LServer, TS, PushJID, Node, XData) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
delete_session(LUser, LServer, PushJID, Node),
@@ -460,7 +471,7 @@ store_session(LUser, LServer, TS, PushJID, Node, XData) ->
end.
-spec lookup_session(binary(), binary(), timestamp())
- -> {ok, push_session()} | error.
+ -> {ok, push_session()} | error | {error, err_reason()}.
lookup_session(LUser, LServer, TS) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
case use_cache(Mod, LServer) of
@@ -472,7 +483,7 @@ lookup_session(LUser, LServer, TS) ->
Mod:lookup_session(LUser, LServer, TS)
end.
--spec lookup_sessions(binary(), binary()) -> {ok, [push_session()]} | error.
+-spec lookup_sessions(binary(), binary()) -> {ok, [push_session()]} | {error, err_reason()}.
lookup_sessions(LUser, LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
case use_cache(Mod, LServer) of
@@ -484,40 +495,48 @@ lookup_sessions(LUser, LServer) ->
Mod:lookup_sessions(LUser, LServer)
end.
--spec delete_session(binary(), binary(), timestamp()) -> ok | error.
+-spec delete_session(binary(), binary(), timestamp()) -> ok | {error, db_failure}.
delete_session(LUser, LServer, TS) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
- ok = Mod:delete_session(LUser, LServer, TS),
- case use_cache(Mod, LServer) of
- true ->
- ets_cache:delete(?PUSH_CACHE, {LUser, LServer},
- cache_nodes(Mod, LServer)),
- ets_cache:delete(?PUSH_CACHE, {LUser, LServer, TS},
- cache_nodes(Mod, LServer));
- false ->
- ok
+ case Mod:delete_session(LUser, LServer, TS) of
+ ok ->
+ case use_cache(Mod, LServer) of
+ true ->
+ ets_cache:delete(?PUSH_CACHE, {LUser, LServer},
+ cache_nodes(Mod, LServer)),
+ ets_cache:delete(?PUSH_CACHE, {LUser, LServer, TS},
+ cache_nodes(Mod, LServer));
+ false ->
+ ok
+ end;
+ {error, _} = Err ->
+ Err
end.
--spec delete_session(binary(), binary(), jid(), binary()) -> ok | error.
+-spec delete_session(binary(), binary(), jid(), binary()) -> ok | {error, err_reason()}.
delete_session(LUser, LServer, PushJID, Node) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:lookup_session(LUser, LServer, PushJID, Node) of
{ok, {TS, _, _, _}} ->
delete_session(LUser, LServer, TS);
error ->
- error
+ {error, notfound};
+ {error, _} = Err ->
+ Err
end.
--spec delete_sessions(binary(), binary(), jid()) -> ok | error.
+-spec delete_sessions(binary(), binary(), jid()) -> ok | {error, err_reason()}.
delete_sessions(LUser, LServer, PushJID) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
LookupFun = fun() -> Mod:lookup_sessions(LUser, LServer, PushJID) end,
delete_sessions(LUser, LServer, LookupFun, Mod).
--spec delete_sessions(binary(), binary(), fun(() -> ok | error), module())
- -> ok | error.
+-spec delete_sessions(binary(), binary(), fun(() -> any()), module())
+ -> ok | {error, err_reason()}.
delete_sessions(LUser, LServer, LookupFun, Mod) ->
case LookupFun() of
+ {ok, []} ->
+ {error, notfound};
{ok, Clients} ->
case use_cache(Mod, LServer) of
true ->
@@ -538,8 +557,8 @@ delete_sessions(LUser, LServer, LookupFun, Mod) ->
ok
end
end, Clients);
- error ->
- error
+ {error, _} = Err ->
+ Err
end.
-spec drop_online_sessions(binary(), binary(), [push_session()])
diff --git a/src/mod_push_mnesia.erl b/src/mod_push_mnesia.erl
index 309ff80e3..ff12150f2 100644
--- a/src/mod_push_mnesia.erl
+++ b/src/mod_push_mnesia.erl
@@ -31,18 +31,12 @@
%% API
-export([init/2, store_session/6, lookup_session/4, lookup_session/3,
lookup_sessions/3, lookup_sessions/2, lookup_sessions/1,
- delete_session/3, delete_old_sessions/2]).
+ delete_session/3, delete_old_sessions/2, transform/1]).
-include_lib("stdlib/include/ms_transform.hrl").
-include("logger.hrl").
-include("xmpp.hrl").
-
--record(push_session,
- {us = {<<"">>, <<"">>} :: {binary(), binary()},
- timestamp = p1_time_compat:timestamp() :: erlang:timestamp(),
- service = {<<"">>, <<"">>, <<"">>} :: ljid(),
- node = <<"">> :: binary(),
- xdata = #xdata{} :: xdata()}).
+-include("mod_push.hrl").
%%%-------------------------------------------------------------------
%%% API
@@ -67,7 +61,7 @@ store_session(LUser, LServer, TS, PushJID, Node, XData) ->
timestamp = TS,
service = PushLJID,
node = Node,
- xdata = XData})
+ xml = encode_xdata(XData)})
end,
case mnesia:transaction(F) of
{atomic, ok} ->
@@ -75,7 +69,7 @@ store_session(LUser, LServer, TS, PushJID, Node, XData) ->
{aborted, E} ->
?ERROR_MSG("Cannot store push session for ~s@~s: ~p",
[LUser, LServer, E]),
- error
+ {error, db_failure}
end.
lookup_session(LUser, LServer, PushJID, Node) ->
@@ -89,12 +83,12 @@ lookup_session(LUser, LServer, PushJID, Node) ->
Rec
end),
case mnesia:dirty_select(push_session, MatchSpec) of
- [#push_session{timestamp = TS, xdata = XData}] ->
- {ok, {TS, PushLJID, Node, XData}};
- _ ->
+ [#push_session{timestamp = TS, xml = El}] ->
+ {ok, {TS, PushLJID, Node, decode_xdata(El)}};
+ [] ->
?DEBUG("No push session found for ~s@~s (~p, ~s)",
[LUser, LServer, PushJID, Node]),
- error
+ {error, notfound}
end.
lookup_session(LUser, LServer, TS) ->
@@ -106,33 +100,31 @@ lookup_session(LUser, LServer, TS) ->
Rec
end),
case mnesia:dirty_select(push_session, MatchSpec) of
- [#push_session{service = PushLJID, node = Node, xdata = XData}] ->
- {ok, {TS, PushLJID, Node, XData}};
- _ ->
+ [#push_session{service = PushLJID, node = Node, xml = El}] ->
+ {ok, {TS, PushLJID, Node, decode_xdata(El)}};
+ [] ->
?DEBUG("No push session found for ~s@~s (~p)",
[LUser, LServer, TS]),
- error
+ {error, notfound}
end.
lookup_sessions(LUser, LServer, PushJID) ->
PushLJID = jid:tolower(PushJID),
MatchSpec = ets:fun2ms(
- fun(#push_session{us = {U, S}, service = P, node = N} = Rec)
+ fun(#push_session{us = {U, S}, service = P,
+ node = Node, timestamp = TS,
+ xml = El} = Rec)
when U == LUser,
S == LServer,
P == PushLJID ->
Rec
end),
- {ok, mnesia:dirty_select(push_session, MatchSpec)}.
+ Records = mnesia:dirty_select(push_session, MatchSpec),
+ {ok, records_to_sessions(Records)}.
lookup_sessions(LUser, LServer) ->
Records = mnesia:dirty_read(push_session, {LUser, LServer}),
- Clients = [{TS, PushLJID, Node, XData}
- || #push_session{timestamp = TS,
- service = PushLJID,
- node = Node,
- xdata = XData} <- Records],
- {ok, Clients}.
+ {ok, records_to_sessions(Records)}.
lookup_sessions(LServer) ->
MatchSpec = ets:fun2ms(
@@ -140,11 +132,12 @@ lookup_sessions(LServer) ->
timestamp = TS,
service = PushLJID,
node = Node,
- xdata = XData})
+ xml = El})
when S == LServer ->
- {TS, PushLJID, Node, XData}
+ {TS, PushLJID, Node, El}
end),
- {ok, mnesia:dirty_select(push_session, MatchSpec)}.
+ Records = mnesia:dirty_select(push_session, MatchSpec),
+ {ok, records_to_sessions(Records)}.
delete_session(LUser, LServer, TS) ->
MatchSpec = ets:fun2ms(
@@ -164,7 +157,7 @@ delete_session(LUser, LServer, TS) ->
{aborted, E} ->
?ERROR_MSG("Cannot delete push session of ~s@~s: ~p",
[LUser, LServer, E]),
- error
+ {error, db_failure}
end.
delete_old_sessions(_LServer, Time) ->
@@ -181,9 +174,14 @@ delete_old_sessions(_LServer, Time) ->
ok;
{aborted, E} ->
?ERROR_MSG("Cannot delete old push sessions: ~p", [E]),
- error
+ {error, db_failure}
end.
+transform({push_session, US, TS, Service, Node, XData}) ->
+ ?INFO_MSG("Transforming push_session Mnesia table", []),
+ #push_session{us = US, timestamp = TS, service = Service,
+ node = Node, xml = encode_xdata(XData)}.
+
%%--------------------------------------------------------------------
%% Internal functions.
%%--------------------------------------------------------------------
@@ -202,3 +200,20 @@ enforce_max_sessions({U, S} = US, Max) ->
true ->
ok
end.
+
+decode_xdata(undefined) ->
+ undefined;
+decode_xdata(El) ->
+ xmpp:decode(El).
+
+encode_xdata(undefined) ->
+ undefined;
+encode_xdata(XData) ->
+ xmpp:encode(XData).
+
+records_to_sessions(Records) ->
+ [{TS, PushLJID, Node, decode_xdata(El)}
+ || #push_session{timestamp = TS,
+ service = PushLJID,
+ node = Node,
+ xml = El} <- Records].
diff --git a/src/mod_push_sql.erl b/src/mod_push_sql.erl
new file mode 100644
index 000000000..e4634a34e
--- /dev/null
+++ b/src/mod_push_sql.erl
@@ -0,0 +1,221 @@
+%%%----------------------------------------------------------------------
+%%% ejabberd, Copyright (C) 2017 ProcessOne
+%%%
+%%% This program is free software; you can redistribute it and/or
+%%% modify it under the terms of the GNU General Public License as
+%%% published by the Free Software Foundation; either version 2 of the
+%%% License, or (at your option) any later version.
+%%%
+%%% This program is distributed in the hope that it will be useful,
+%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
+%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%%% General Public License for more details.
+%%%
+%%% You should have received a copy of the GNU General Public License along
+%%% with this program; if not, write to the Free Software Foundation, Inc.,
+%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+%%%
+%%%----------------------------------------------------------------------
+-module(mod_push_sql).
+-behaviour(mod_push).
+-compile([{parse_transform, ejabberd_sql_pt}]).
+
+%% API
+-export([init/2, store_session/6, lookup_session/4, lookup_session/3,
+ lookup_sessions/3, lookup_sessions/2, lookup_sessions/1,
+ delete_session/3, delete_old_sessions/2, export/1]).
+
+-include("xmpp.hrl").
+-include("logger.hrl").
+-include("ejabberd_sql_pt.hrl").
+-include("mod_push.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_Host, _Opts) ->
+ ok.
+
+store_session(LUser, LServer, NowTS, PushJID, Node, XData) ->
+ XML = encode_xdata(XData),
+ TS = misc:now_to_usec(NowTS),
+ PushLJID = jid:tolower(PushJID),
+ Service = jid:encode(PushLJID),
+ case ?SQL_UPSERT(LServer, "push_session",
+ ["!username=%(LUser)s",
+ "!timestamp=%(TS)d",
+ "!service=%(Service)s",
+ "!node=%(Node)s",
+ "xml=%(XML)s"]) of
+ ok ->
+ {ok, {NowTS, PushLJID, Node, XData}};
+ Err ->
+ ?ERROR_MSG("Failed to update 'push_session' table: ~p", [Err]),
+ {error, db_failure}
+ end.
+
+lookup_session(LUser, LServer, PushJID, Node) ->
+ PushLJID = jid:tolower(PushJID),
+ Service = jid:encode(PushLJID),
+ case ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(timestamp)d, @(xml)s from push_session "
+ "where username=%(LUser)s and service=%(Service)s "
+ "and node=%(Node)s")) of
+ {selected, [{TS, XML}]} ->
+ NowTS = misc:usec_to_now(TS),
+ XData = decode_xdata(XML, LUser, LServer),
+ {ok, {NowTS, PushLJID, Node, XData}};
+ {selected, []} ->
+ {error, notfound};
+ Err ->
+ ?ERROR_MSG("Failed to select from 'push_session' table: ~p", [Err]),
+ {error, db_failure}
+ end.
+
+lookup_session(LUser, LServer, NowTS) ->
+ TS = misc:now_to_usec(NowTS),
+ case ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(service)s, @(node)s, @(xml)s "
+ "from push_session where username=%(LUser)s "
+ "and timestamp=%(TS)d")) of
+ {selected, [{Service, Node, XML}]} ->
+ PushLJID = jid:tolower(jid:decode(Service)),
+ XData = decode_xdata(XML, LUser, LServer),
+ {ok, {NowTS, PushLJID, Node, XData}};
+ {selected, []} ->
+ {error, notfound};
+ Err ->
+ ?ERROR_MSG("Failed to select from 'push_session' table: ~p", [Err]),
+ {error, db_failure}
+ end.
+
+lookup_sessions(LUser, LServer, PushJID) ->
+ PushLJID = jid:tolower(PushJID),
+ Service = jid:encode(PushLJID),
+ case ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(timestamp)d, @(xml)s, @(node)s from push_session "
+ "where username=%(LUser)s and service=%(Service)s")) of
+ {selected, Rows} ->
+ {ok, lists:map(
+ fun({TS, XML, Node}) ->
+ NowTS = misc:usec_to_now(TS),
+ XData = decode_xdata(XML, LUser, LServer),
+ {NowTS, PushLJID, Node, XData}
+ end, Rows)};
+ Err ->
+ ?ERROR_MSG("Failed to select from 'push_session' table: ~p", [Err]),
+ {error, db_failure}
+ end.
+
+lookup_sessions(LUser, LServer) ->
+ case ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(timestamp)d, @(xml)s, @(node)s, @(service)s "
+ "from push_session where username=%(LUser)s")) of
+ {selected, Rows} ->
+ {ok, lists:map(
+ fun({TS, XML, Node, Service}) ->
+ NowTS = misc:usec_to_now(TS),
+ XData = decode_xdata(XML, LUser, LServer),
+ PushLJID = jid:tolower(jid:decode(Service)),
+ {NowTS, PushLJID,Node, XData}
+ end, Rows)};
+ Err ->
+ ?ERROR_MSG("Failed to select from 'push_session' table: ~p", [Err]),
+ {error, db_failure}
+ end.
+
+lookup_sessions(LServer) ->
+ case ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(username)s, @(timestamp)d, @(xml)s, "
+ "@(node)s, @(service)s from push_session")) of
+ {selected, Rows} ->
+ {ok, lists:map(
+ fun({LUser, TS, XML, Node, Service}) ->
+ NowTS = misc:usec_to_now(TS),
+ XData = decode_xdata(XML, LUser, LServer),
+ PushLJID = jid:tolower(jid:decode(Service)),
+ {NowTS, PushLJID, Node, XData}
+ end, Rows)};
+ Err ->
+ ?ERROR_MSG("Failed to select from 'push_session' table: ~p", [Err]),
+ {error, db_failure}
+ end.
+
+delete_session(LUser, LServer, NowTS) ->
+ TS = misc:now_to_usec(NowTS),
+ case ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("delete from push_session where "
+ "username=%(LUser)s and timestamp=%(TS)d")) of
+ {updated, _} ->
+ ok;
+ Err ->
+ ?ERROR_MSG("failed to delete from 'push_session' table: ~p", [Err]),
+ {error, db_failure}
+ end.
+
+delete_old_sessions(LServer, Time) ->
+ TS = misc:now_to_usec(Time),
+ case ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("delete from push_session where timestamp<%(TS)d")) of
+ {updated, _} ->
+ ok;
+ Err ->
+ ?ERROR_MSG("failed to delete from 'push_session' table: ~p", [Err]),
+ {error, db_failure}
+ end.
+
+export(_Server) ->
+ [{push_session,
+ fun(Host, #push_session{us = {LUser, LServer},
+ timestamp = NowTS,
+ service = PushLJID,
+ node = Node,
+ xml = XData})
+ when LServer == Host ->
+ TS = misc:now_to_usec(NowTS),
+ Service = jid:encode(PushLJID),
+ XML = encode_xdata(XData),
+ [?SQL("delete from push_session where "
+ "username=%(LUser)s and timestamp=%(TS)d and "
+ "service=%(Service)s and node=%(Node)s and "
+ "xml=%(XML)s;"),
+ ?SQL("insert into push_session(username, timestamp, "
+ "service, node, xml) values ("
+ "%(LUser)s, %(TS)d, %(Service)s, %(Node)s, %(XML)s);")];
+ (_Host, _R) ->
+ []
+ end}].
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+decode_xdata(<<>>, _LUser, _LServer) ->
+ undefined;
+decode_xdata(XML, LUser, LServer) ->
+ case fxml_stream:parse_element(XML) of
+ #xmlel{} = El ->
+ try xmpp:decode(El)
+ catch _:{xmpp_codec, Why} ->
+ ?ERROR_MSG("Failed to decode ~s for user ~s@~s "
+ "from table 'push_session': ~s",
+ [XML, LUser, LServer, xmpp:format_error(Why)]),
+ undefined
+ end;
+ Err ->
+ ?ERROR_MSG("Failed to decode ~s for user ~s@~s from "
+ "table 'push_session': ~p",
+ [XML, LUser, LServer, Err]),
+ undefined
+ end.
+
+encode_xdata(undefined) ->
+ <<>>;
+encode_xdata(XData) ->
+ fxml:element_to_binary(xmpp:encode(XData)).
diff --git a/src/mod_register_web.erl b/src/mod_register_web.erl
index 16c2d8020..b7bc2edca 100644
--- a/src/mod_register_web.erl
+++ b/src/mod_register_web.erl
@@ -156,10 +156,14 @@ process(_Path, _Request) ->
%%%----------------------------------------------------------------------
serve_css() ->
- {200,
- [{<<"Content-Type">>, <<"text/css">>}, last_modified(),
- cache_control_public()],
- css()}.
+ case css() of
+ {ok, CSS} ->
+ {200,
+ [{<<"Content-Type">>, <<"text/css">>}, last_modified(),
+ cache_control_public()], CSS};
+ error ->
+ {404, [], "CSS not found"}
+ end.
last_modified() ->
{<<"Last-Modified">>,
@@ -168,16 +172,30 @@ last_modified() ->
cache_control_public() ->
{<<"Cache-Control">>, <<"public">>}.
+-spec css() -> {ok, binary()} | error.
css() ->
- <<"html,body {\nbackground: white;\nmargin: "
- "0;\npadding: 0;\nheight: 100%;\n}">>.
+ Dir = misc:css_dir(),
+ File = filename:join(Dir, "register.css"),
+ case file:read_file(File) of
+ {ok, Data} ->
+ {ok, Data};
+ {error, Why} ->
+ ?ERROR_MSG("failed to read ~s: ~s", [File, file:format_error(Why)]),
+ error
+ end.
+
+meta() ->
+ ?XA(<<"meta">>,
+ [{<<"name">>, <<"viewport">>},
+ {<<"content">>, <<"width=device-width, initial-scale=1">>}]).
%%%----------------------------------------------------------------------
%%% Index page
%%%----------------------------------------------------------------------
index_page(Lang) ->
- HeadEls = [?XCT(<<"title">>,
+ HeadEls = [meta(),
+ ?XCT(<<"title">>,
<<"Jabber Account Registration">>),
?XA(<<"link">>,
[{<<"href">>, <<"/register/register.css">>},
@@ -206,7 +224,8 @@ index_page(Lang) ->
form_new_get(Host, Lang, IP) ->
CaptchaEls = build_captcha_li_list(Lang, IP),
- HeadEls = [?XCT(<<"title">>,
+ HeadEls = [meta(),
+ ?XCT(<<"title">>,
<<"Register a Jabber account">>),
?XA(<<"link">>,
[{<<"href">>, <<"/register/register.css">>},
@@ -350,7 +369,8 @@ build_captcha_li_list2(Lang, IP) ->
%%%----------------------------------------------------------------------
form_changepass_get(Host, Lang) ->
- HeadEls = [?XCT(<<"title">>, <<"Change Password">>),
+ HeadEls = [meta(),
+ ?XCT(<<"title">>, <<"Change Password">>),
?XA(<<"link">>,
[{<<"href">>, <<"/register/register.css">>},
{<<"type">>, <<"text/css">>},
@@ -456,7 +476,8 @@ check_password(Username, Host, Password) ->
%%%----------------------------------------------------------------------
form_del_get(Host, Lang) ->
- HeadEls = [?XCT(<<"title">>,
+ HeadEls = [meta(),
+ ?XCT(<<"title">>,
<<"Unregister a Jabber account">>),
?XA(<<"link">>,
[{<<"href">>, <<"/register/register.css">>},
diff --git a/src/mod_s2s_dialback.erl b/src/mod_s2s_dialback.erl
index 403ac9cce..b4c2ed9df 100644
--- a/src/mod_s2s_dialback.erl
+++ b/src/mod_s2s_dialback.erl
@@ -353,9 +353,9 @@ format_stanza_error(#stanza_error{reason = Reason, text = Txt}) ->
#redirect{} -> <<"redirect">>;
_ -> erlang:atom_to_binary(Reason, latin1)
end,
- case Txt of
- undefined -> Slogan;
- #text{data = <<"">>} -> Slogan;
- #text{data = Data} ->
+ case xmpp:get_text(Txt) of
+ <<"">> ->
+ Slogan;
+ Data ->
<<Data/binary, " (", Slogan/binary, ")">>
end.
diff --git a/src/node_flat.erl b/src/node_flat.erl
index 2c11edde7..18d4f4745 100644
--- a/src/node_flat.erl
+++ b/src/node_flat.erl
@@ -88,7 +88,6 @@ options() ->
{max_payload_size, ?MAX_PAYLOAD_SIZE},
{send_last_published_item, on_sub_and_presence},
{deliver_notifications, true},
- {title, <<>>},
{presence_based_delivery, false},
{itemreply, none}].
@@ -446,21 +445,30 @@ delete_item(Nidx, Publisher, PublishModel, ItemId) ->
case Affiliation of
owner ->
{result, States} = get_states(Nidx),
+ Records = States ++ mnesia:read({pubsub_orphan, Nidx}),
lists:foldl(fun
- (#pubsub_state{items = PI} = S, Res) ->
- case lists:member(ItemId, PI) of
+ (#pubsub_state{items = RI} = S, Res) ->
+ case lists:member(ItemId, RI) of
true ->
- Nitems = lists:delete(ItemId, PI),
+ NI = lists:delete(ItemId, RI),
del_item(Nidx, ItemId),
- set_state(S#pubsub_state{items = Nitems}),
+ mnesia:write(S#pubsub_state{items = NI}),
{result, {default, broadcast}};
false ->
Res
end;
- (_, Res) ->
- Res
+ (#pubsub_orphan{items = RI} = S, Res) ->
+ case lists:member(ItemId, RI) of
+ true ->
+ NI = lists:delete(ItemId, RI),
+ del_item(Nidx, ItemId),
+ mnesia:write(S#pubsub_orphan{items = NI}),
+ {result, {default, broadcast}};
+ false ->
+ Res
+ end
end,
- {error, xmpp:err_item_not_found()}, States);
+ {error, xmpp:err_item_not_found()}, Records);
_ ->
{error, xmpp:err_forbidden()}
end
diff --git a/src/nodetree_tree_sql.erl b/src/nodetree_tree_sql.erl
index 3af035f6c..0b05a885e 100644
--- a/src/nodetree_tree_sql.erl
+++ b/src/nodetree_tree_sql.erl
@@ -287,7 +287,7 @@ raw_to_node(Host, {Node, Parent, Type, Nidx}) ->
Module = misc:binary_to_atom(<<"node_", Type/binary, "_sql">>),
StdOpts = Module:options(),
lists:foldl(fun ({Key, Value}, Acc) ->
- lists:keyreplace(Key, 1, Acc, {Key, Value})
+ lists:keystore(Key, 1, Acc, {Key, Value})
end,
StdOpts, DbOpts);
_ ->
diff --git a/src/xmpp_stream_in.erl b/src/xmpp_stream_in.erl
index d9be3d4f7..329ebad61 100644
--- a/src/xmpp_stream_in.erl
+++ b/src/xmpp_stream_in.erl
@@ -1117,17 +1117,17 @@ format_inet_error(Reason) ->
Txt -> Txt
end.
--spec format_stream_error(atom() | 'see-other-host'(), undefined | text()) -> string().
+-spec format_stream_error(atom() | 'see-other-host'(), [text()]) -> string().
format_stream_error(Reason, Txt) ->
Slogan = case Reason of
undefined -> "no reason";
#'see-other-host'{} -> "see-other-host";
_ -> atom_to_list(Reason)
end,
- case Txt of
- undefined -> Slogan;
- #text{data = <<"">>} -> Slogan;
- #text{data = Data} ->
+ case xmpp:get_text(Txt) of
+ <<"">> ->
+ Slogan;
+ Data ->
binary_to_list(Data) ++ " (" ++ Slogan ++ ")"
end.
diff --git a/src/xmpp_stream_out.erl b/src/xmpp_stream_out.erl
index 982be4dee..7ddc183bf 100644
--- a/src/xmpp_stream_out.erl
+++ b/src/xmpp_stream_out.erl
@@ -25,6 +25,7 @@
-protocol({rfc, 6120}).
-protocol({xep, 114, '1.6'}).
+-protocol({xep, 368, '1.0.0'}).
%% API
-export([start/3, start_link/3, call/3, cast/2, reply/2, connect/1,
@@ -48,8 +49,9 @@
-type state() :: map().
-type noreply() :: {noreply, state(), timeout()}.
--type host_port() :: {inet:hostname(), inet:port_number()}.
--type ip_port() :: {inet:ip_address(), inet:port_number()}.
+-type host_port() :: {inet:hostname(), inet:port_number(), boolean()}.
+-type ip_port() :: {inet:ip_address(), inet:port_number(), boolean()}.
+-type h_addr_list() :: {{integer(), integer(), inet:port_number(), string()}, boolean()}.
-type network_error() :: {error, inet:posix() | inet_res:res_error()}.
-type tls_error_reason() :: inet:posix() | atom() | binary().
-type socket_error_reason() :: inet:posix() | atom().
@@ -278,11 +280,11 @@ handle_cast(connect, #{remote_server := RemoteServer,
process_stream_end({idna, bad_string}, State);
ASCIIName ->
case resolve(binary_to_list(ASCIIName), State) of
- {{ok, AddrPorts}, Encrypted} ->
- case connect(AddrPorts, State, Encrypted) of
- {ok, Socket, AddrPort} ->
+ {ok, AddrPorts} ->
+ case connect(AddrPorts, State) of
+ {ok, Socket, {Addr, Port, Encrypted}} ->
SocketMonitor = SockMod:monitor(Socket),
- State1 = State#{ip => AddrPort,
+ State1 = State#{ip => {Addr, Port},
socket => Socket,
stream_encrypted => Encrypted,
socket_monitor => SocketMonitor},
@@ -291,7 +293,7 @@ handle_cast(connect, #{remote_server := RemoteServer,
{error, {Class, Why}} ->
process_stream_end({Class, Why}, State)
end;
- {{error, Why}, _} ->
+ {error, Why} ->
process_stream_end({dns, Why}, State)
end
end);
@@ -796,17 +798,17 @@ format_inet_error(Reason) ->
Txt -> Txt
end.
--spec format_stream_error(atom() | 'see-other-host'(), undefined | text()) -> string().
+-spec format_stream_error(atom() | 'see-other-host'(), [text()]) -> string().
format_stream_error(Reason, Txt) ->
Slogan = case Reason of
undefined -> "no reason";
#'see-other-host'{} -> "see-other-host";
_ -> atom_to_list(Reason)
end,
- case Txt of
- undefined -> Slogan;
- #text{data = <<"">>} -> Slogan;
- #text{data = Data} ->
+ case xmpp:get_text(Txt) of
+ <<"">> ->
+ Slogan;
+ Data ->
binary_to_list(Data) ++ " (" ++ Slogan ++ ")"
end.
@@ -854,79 +856,92 @@ idna_to_ascii(Host) ->
{error, _} -> ejabberd_idna:domain_utf8_to_ascii(Host)
end.
--spec resolve(string(), state()) -> {{ok, [ip_port()]} | network_error(), boolean()}.
+-spec resolve(string(), state()) -> {ok, [ip_port()]} | network_error().
resolve(Host, State) ->
case srv_lookup(Host, State) of
- {{error, _Reason}, _} ->
+ {error, _Reason} ->
DefaultPort = get_default_port(State),
- {a_lookup([{Host, DefaultPort}], State), false};
- {{ok, HostPorts}, TLS} ->
- {a_lookup(HostPorts, State), TLS}
+ a_lookup([{Host, DefaultPort, false}], State);
+ {ok, HostPorts} ->
+ a_lookup(HostPorts, State)
end.
--spec srv_lookup(string(), state()) -> {{ok, [host_port()]} | network_error(), boolean()}.
+-spec srv_lookup(string(), state()) -> {ok, [host_port()]} | network_error().
srv_lookup(_Host, #{xmlns := ?NS_COMPONENT}) ->
%% Do not attempt to lookup SRV for component connections
- {{error, nxdomain}, false};
+ {error, nxdomain};
srv_lookup(Host, State) ->
%% Only perform SRV lookups for FQDN names
case string:chr(Host, $.) of
0 ->
- {{error, nxdomain}, false};
+ {error, nxdomain};
_ ->
case inet:parse_address(Host) of
{ok, _} ->
- {{error, nxdomain}, false};
+ {error, nxdomain};
{error, _} ->
Timeout = get_dns_timeout(State),
Retries = get_dns_retries(State),
- case is_starttls_available(State) of
- true ->
- case srv_lookup("_xmpps-server._tcp." ++ Host,
- Timeout, Retries) of
- {error, _} ->
- {srv_lookup("_xmpp-server._tcp." ++ Host,
- Timeout, Retries),
- false};
- {ok, Res} ->
- {{ok, Res}, true}
- end;
- false ->
- {srv_lookup("_xmpp-server._tcp." ++ Host,
- Timeout, Retries),
- false}
+ case srv_lookup(Host, State, Timeout, Retries) of
+ {ok, AddrList} ->
+ h_addr_list_to_host_ports(AddrList);
+ {error, _} = Err ->
+ Err
end
end
end.
+srv_lookup(Host, State, Timeout, Retries) ->
+ TLSAddrs = case is_starttls_available(State) of
+ true ->
+ case srv_lookup("_xmpps-server._tcp." ++ Host,
+ Timeout, Retries) of
+ {ok, HostEnt} ->
+ [{A, true} || A <- HostEnt#hostent.h_addr_list];
+ {error, _} ->
+ []
+ end;
+ false ->
+ []
+ end,
+ case srv_lookup("_xmpp-server._tcp." ++ Host, Timeout, Retries) of
+ {ok, HostEntry} ->
+ Addrs = [{A, false} || A <- HostEntry#hostent.h_addr_list],
+ {ok, TLSAddrs ++ Addrs};
+ {error, _} when TLSAddrs /= [] ->
+ {ok, TLSAddrs};
+ {error, _} = Err ->
+ Err
+ end.
+
-spec srv_lookup(string(), timeout(), integer()) ->
- {ok, [host_port()]} | network_error().
+ {ok, inet:hostent()} | network_error().
srv_lookup(_SRVName, _Timeout, Retries) when Retries < 1 ->
{error, timeout};
srv_lookup(SRVName, Timeout, Retries) ->
case inet_res:getbyname(SRVName, srv, Timeout) of
{ok, HostEntry} ->
- host_entry_to_host_ports(HostEntry);
+ {ok, HostEntry};
{error, timeout} ->
srv_lookup(SRVName, Timeout, Retries - 1);
{error, _} = Err ->
Err
end.
--spec a_lookup([{inet:hostname(), inet:port_number()}], state()) ->
+-spec a_lookup([host_port()], state()) ->
{ok, [ip_port()]} | network_error().
a_lookup(HostPorts, State) ->
- HostPortFamilies = [{Host, Port, Family}
- || {Host, Port} <- HostPorts,
+ HostPortFamilies = [{Host, Port, TLS, Family}
+ || {Host, Port, TLS} <- HostPorts,
Family <- get_address_families(State)],
a_lookup(HostPortFamilies, State, [], {error, nxdomain}).
--spec a_lookup([{inet:hostname(), inet:port_number(), inet:address_family()}],
+-spec a_lookup([{inet:hostname(), inet:port_number(), boolean(), inet:address_family()}],
state(), [ip_port()], network_error()) -> {ok, [ip_port()]} | network_error().
-a_lookup([{Host, Port, Family}|HostPortFamilies], State, Acc, Err) ->
+a_lookup([{Host, Port, TLS, Family}|HostPortFamilies], State, Acc, Err) ->
Timeout = get_dns_timeout(State),
Retries = get_dns_retries(State),
- case a_lookup(Host, Port, Family, Timeout, Retries) of
+ case a_lookup(Host, Port, TLS, Family, Timeout, Retries) of
{error, Reason} ->
a_lookup(HostPortFamilies, State, Acc, {error, Reason});
{ok, AddrPorts} ->
@@ -937,11 +952,11 @@ a_lookup([], _State, [], Err) ->
a_lookup([], _State, Acc, _) ->
{ok, Acc}.
--spec a_lookup(inet:hostname(), inet:port_number(), inet:address_family(),
+-spec a_lookup(inet:hostname(), inet:port_number(), boolean(), inet:address_family(),
timeout(), integer()) -> {ok, [ip_port()]} | network_error().
-a_lookup(_Host, _Port, _Family, _Timeout, Retries) when Retries < 1 ->
+a_lookup(_Host, _Port, _TLS, _Family, _Timeout, Retries) when Retries < 1 ->
{error, timeout};
-a_lookup(Host, Port, Family, Timeout, Retries) ->
+a_lookup(Host, Port, TLS, Family, Timeout, Retries) ->
Start = p1_time_compat:monotonic_time(milli_seconds),
case inet:gethostbyname(Host, Family, Timeout) of
{error, nxdomain} = Err ->
@@ -952,43 +967,43 @@ a_lookup(Host, Port, Family, Timeout, Retries) ->
%% it ignores DNS configuration settings (/etc/hosts, etc)
End = p1_time_compat:monotonic_time(milli_seconds),
if (End - Start) >= Timeout ->
- a_lookup(Host, Port, Family, Timeout, Retries - 1);
+ a_lookup(Host, Port, TLS, Family, Timeout, Retries - 1);
true ->
Err
end;
{error, _} = Err ->
Err;
{ok, HostEntry} ->
- host_entry_to_addr_ports(HostEntry, Port)
+ host_entry_to_addr_ports(HostEntry, Port, TLS)
end.
--spec host_entry_to_host_ports(inet:hostent()) -> {ok, [host_port()]} |
+-spec h_addr_list_to_host_ports(h_addr_list()) -> {ok, [host_port()]} |
{error, nxdomain}.
-host_entry_to_host_ports(#hostent{h_addr_list = AddrList}) ->
+h_addr_list_to_host_ports(AddrList) ->
PrioHostPorts = lists:flatmap(
- fun({Priority, Weight, Port, Host}) ->
+ fun({{Priority, Weight, Port, Host}, TLS}) ->
N = case Weight of
0 -> 0;
_ -> (Weight + 1) * randoms:uniform()
end,
- [{Priority * 65536 - N, Host, Port}];
+ [{Priority * 65536 - N, Host, Port, TLS}];
(_) ->
[]
end, AddrList),
- HostPorts = [{Host, Port}
- || {_Priority, Host, Port} <- lists:usort(PrioHostPorts)],
+ HostPorts = [{Host, Port, TLS}
+ || {_Priority, Host, Port, TLS} <- lists:usort(PrioHostPorts)],
case HostPorts of
[] -> {error, nxdomain};
_ -> {ok, HostPorts}
end.
--spec host_entry_to_addr_ports(inet:hostent(), inet:port_number()) ->
+-spec host_entry_to_addr_ports(inet:hostent(), inet:port_number(), boolean()) ->
{ok, [ip_port()]} | {error, nxdomain}.
-host_entry_to_addr_ports(#hostent{h_addr_list = AddrList}, Port) ->
+host_entry_to_addr_ports(#hostent{h_addr_list = AddrList}, Port, TLS) ->
AddrPorts = lists:flatmap(
fun(Addr) ->
try get_addr_type(Addr) of
- _ -> [{Addr, Port}]
+ _ -> [{Addr, Port, TLS}]
catch _:_ ->
[]
end
@@ -998,26 +1013,26 @@ host_entry_to_addr_ports(#hostent{h_addr_list = AddrList}, Port) ->
_ -> {ok, AddrPorts}
end.
--spec connect([ip_port()], state(), boolean()) -> {ok, term(), ip_port()} |
- {error, {socket, socket_error_reason()}} |
- {error, {tls, tls_error_reason()}}.
-connect(AddrPorts, #{sockmod := SockMod} = State, TLS) ->
+-spec connect([ip_port()], state()) -> {ok, term(), ip_port()} |
+ {error, {socket, socket_error_reason()}} |
+ {error, {tls, tls_error_reason()}}.
+connect(AddrPorts, #{sockmod := SockMod} = State) ->
Timeout = get_connect_timeout(State),
case connect(AddrPorts, SockMod, Timeout, {error, nxdomain}) of
- {ok, Socket, AddrPort} when TLS ->
+ {ok, Socket, {Addr, Port, TLS = true}} ->
case starttls(Socket, State) of
- {ok, TLSSocket} -> {ok, TLSSocket, AddrPort};
+ {ok, TLSSocket} -> {ok, TLSSocket, {Addr, Port, TLS}};
{error, Why} -> {error, {tls, Why}}
end;
- {ok, _Socket, _AddrPort} = OK ->
- OK;
+ {ok, Socket, {Addr, Port, TLS = false}} ->
+ {ok, Socket, {Addr, Port, TLS}};
{error, Why} ->
{error, {socket, Why}}
end.
-spec connect([ip_port()], module(), timeout(), network_error()) ->
{ok, term(), ip_port()} | network_error().
-connect([{Addr, Port}|AddrPorts], SockMod, Timeout, _) ->
+connect([{Addr, Port, TLS}|AddrPorts], SockMod, Timeout, _) ->
Type = get_addr_type(Addr),
try SockMod:connect(Addr, Port,
[binary, {packet, 0},
@@ -1026,7 +1041,7 @@ connect([{Addr, Port}|AddrPorts], SockMod, Timeout, _) ->
{active, false}, Type],
Timeout) of
{ok, Socket} ->
- {ok, Socket, {Addr, Port}};
+ {ok, Socket, {Addr, Port, TLS}};
Err ->
connect(AddrPorts, SockMod, Timeout, Err)
catch _:badarg ->